Делаем композицию вместо билдера + наследования
This commit is contained in:
parent
a97e51a51d
commit
3617b38196
@ -19,20 +19,20 @@ import ru.dbotthepony.kstarbound.api.PhysicalFile
|
|||||||
import ru.dbotthepony.kstarbound.defs.*
|
import ru.dbotthepony.kstarbound.defs.*
|
||||||
import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration
|
import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration
|
||||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.HarvestingToolPrototype
|
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.IArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.ItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
|
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
|
||||||
@ -85,7 +85,6 @@ import java.lang.ref.ReferenceQueue
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
import java.util.function.BinaryOperator
|
import java.util.function.BinaryOperator
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
@ -1056,16 +1055,16 @@ class Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
private fun loadItemDefinitions(callback: (String) -> Unit, files: Map<String, Collection<IStarboundFile>>) {
|
private fun loadItemDefinitions(callback: (String) -> Unit, files: Map<String, Collection<IStarboundFile>>) {
|
||||||
val fileMap = mapOf(
|
val fileMap = mapOf(
|
||||||
"item" to ItemPrototype::class.java,
|
"item" to ItemDefinition::class.java,
|
||||||
"currency" to CurrencyItemPrototype::class.java,
|
"currency" to CurrencyItemDefinition::class.java,
|
||||||
"liqitem" to LiquidItemPrototype::class.java,
|
"liqitem" to LiquidItemDefinition::class.java,
|
||||||
"matitem" to MaterialItemPrototype::class.java,
|
"matitem" to MaterialItemDefinition::class.java,
|
||||||
"flashlight" to FlashlightPrototype::class.java,
|
"flashlight" to FlashlightDefinition::class.java,
|
||||||
"harvestingtool" to HarvestingToolPrototype::class.java,
|
"harvestingtool" to HarvestingToolPrototype::class.java,
|
||||||
"head" to HeadArmorItemPrototype::class.java,
|
"head" to HeadArmorItemDefinition::class.java,
|
||||||
"chest" to ChestArmorItemPrototype::class.java,
|
"chest" to ChestArmorItemDefinition::class.java,
|
||||||
"legs" to LegsArmorItemPrototype::class.java,
|
"legs" to LegsArmorItemDefinition::class.java,
|
||||||
"back" to BackArmorItemPrototype::class.java,
|
"back" to BackArmorItemDefinition::class.java,
|
||||||
)
|
)
|
||||||
|
|
||||||
for ((ext, clazz) in fileMap) {
|
for ((ext, clazz) in fileMap) {
|
||||||
@ -1075,7 +1074,7 @@ class Starbound : ISBFileLocator {
|
|||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
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)
|
_items.add(def, json, listedFile, gson, pathStack)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
logger.error("Loading item definition file $listedFile", err)
|
logger.error("Loading item definition file $listedFile", err)
|
||||||
|
@ -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<V>(private var value: V? = null) : ReadWriteProperty<Any?, V?>, 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<V : Any>(private var value: V? = null) : ReadWriteProperty<Any?, V>, 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<IProperty>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
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 {
|
interface IScriptable {
|
||||||
/**
|
/**
|
||||||
* Lua скрипты для выполнения
|
* Lua скрипты для выполнения
|
||||||
@ -10,4 +15,10 @@ interface IScriptable {
|
|||||||
* Через какое количество тиков вызывать обновления скриптов
|
* Через какое количество тиков вызывать обновления скриптов
|
||||||
*/
|
*/
|
||||||
val scriptDelta: Int
|
val scriptDelta: Int
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Impl(
|
||||||
|
override val scripts: ImmutableList<DirectAssetReference> = ImmutableList.of(),
|
||||||
|
override val scriptDelta: Int = 1
|
||||||
|
) : IScriptable
|
||||||
}
|
}
|
||||||
|
@ -125,11 +125,15 @@ data class ThingDescription(
|
|||||||
"shortdescription" -> shortdescription = `in`.nextString()
|
"shortdescription" -> shortdescription = `in`.nextString()
|
||||||
"description" -> description = `in`.nextString()
|
"description" -> description = `in`.nextString()
|
||||||
else -> {
|
else -> {
|
||||||
if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) {
|
try {
|
||||||
racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString()))
|
if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) {
|
||||||
} else if (name.endsWith("description") || name.endsWith("Description")) {
|
racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString()))
|
||||||
racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString()))
|
} else if (name.endsWith("description") || name.endsWith("Description")) {
|
||||||
} else {
|
racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString()))
|
||||||
|
} else {
|
||||||
|
`in`.skipValue()
|
||||||
|
}
|
||||||
|
} catch (_: IllegalStateException) {
|
||||||
`in`.skipValue()
|
`in`.skipValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ data class ItemReference(
|
|||||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == ItemReference::class.java) {
|
if (type.rawType == ItemReference::class.java) {
|
||||||
return object : TypeAdapter<ItemReference>() {
|
return object : TypeAdapter<ItemReference>() {
|
||||||
private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = false), 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, logMisses = true, asList = true), 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<RegistryReference<IItemDefinition>>
|
private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter<RegistryReference<IItemDefinition>>
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: ItemReference?) {
|
override fun write(out: JsonWriter, value: ItemReference?) {
|
||||||
|
@ -4,7 +4,7 @@ import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
|||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class JumpProfile(
|
data class JumpProfile(
|
||||||
val jumpSpeed: Double,
|
val jumpSpeed: Double = 0.0,
|
||||||
val jumpInitialPercentage: Double,
|
val jumpInitialPercentage: Double = 0.0,
|
||||||
val jumpHoldTime: Double,
|
val jumpHoldTime: Double = 0.0,
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,10 @@ import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
|||||||
import ru.dbotthepony.kstarbound.defs.RegistryReference
|
import ru.dbotthepony.kstarbound.defs.RegistryReference
|
||||||
import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon
|
import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
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 {
|
interface IItemDefinition : IThingWithDescription {
|
||||||
/**
|
/**
|
||||||
* Внутреннее имя предмета (ID).
|
* Внутреннее имя предмета (ID).
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.item.api
|
package ru.dbotthepony.kstarbound.defs.item.api
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
|
||||||
import ru.dbotthepony.kstarbound.util.Either
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
|
|
||||||
interface ILiquidItem : IItemDefinition {
|
interface ILiquidItem : IItemDefinition {
|
||||||
|
@ -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<Map<String, String>> = ImmutableList.of(),
|
||||||
|
override val maleFrames: IArmorItemDefinition.Frames,
|
||||||
|
override val femaleFrames: IArmorItemDefinition.Frames,
|
||||||
|
override val level: Double = 1.0,
|
||||||
|
override val leveledStatusEffects: ImmutableList<LeveledStatusEffect> = 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"
|
||||||
|
}
|
@ -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<Map<String, String>> 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<LeveledStatusEffect> by NotNull(ImmutableList.of())
|
|
||||||
|
|
||||||
final override var scripts: ImmutableList<DirectAssetReference> 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"
|
|
||||||
}
|
|
@ -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<ICurrencyItemDefinition>.itemType
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,21 +2,23 @@ package ru.dbotthepony.kstarbound.defs.item.impl
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IHarvestingToolDefinition
|
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.JsonBuilder
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
||||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||||
|
|
||||||
@JsonBuilder
|
@JsonFactory
|
||||||
class HarvestingToolPrototype : ItemPrototype(), IHarvestingToolDefinition {
|
class HarvestingToolPrototype(
|
||||||
override var frames: Int by NotNull()
|
@JsonFlat
|
||||||
override var animationCycle: Double by NotNull()
|
val parent: IItemDefinition,
|
||||||
override var blockRadius: Int by NotNull()
|
override val frames: Int,
|
||||||
override var altBlockRadius: Int by NotNull(0)
|
override val animationCycle: Double,
|
||||||
override var idleSound: ImmutableList<String> by NotNull(ImmutableList.of())
|
override val blockRadius: Int,
|
||||||
override var strikeSounds: ImmutableList<String> by NotNull(ImmutableList.of())
|
override val altBlockRadius: Int = 0,
|
||||||
override var handPosition: Vector2d by NotNull()
|
override val idleSound: ImmutableList<String> = ImmutableList.of(),
|
||||||
override var fireTime: Double by NotNull()
|
override val strikeSounds: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override val handPosition: Vector2d,
|
||||||
init {
|
override val fireTime: Double,
|
||||||
maxStack = 1L
|
override val maxStack: Long = 1L,
|
||||||
}
|
) : IItemDefinition by parent, IHarvestingToolDefinition
|
||||||
}
|
|
||||||
|
@ -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<IInventoryIcon>? = null,
|
||||||
|
override var itemTags: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var learnBlueprintsOnPickup: ImmutableList<RegistryReference<IItemDefinition>> = ImmutableList.of(),
|
||||||
|
override var maxStack: Long = 9999L,
|
||||||
|
override var eventCategory: String? = null,
|
||||||
|
override var consumeOnPickup: Boolean = false,
|
||||||
|
override var pickupQuestTemplates: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var tooltipKind: String = "normal",
|
||||||
|
override var twoHanded: Boolean = false,
|
||||||
|
override var radioMessagesOnPickup: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var fuelAmount: Long = 0,
|
||||||
|
override var pickupSoundsSmall: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var pickupSoundsMedium: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var pickupSoundsLarge: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
override var smallStackLimit: Long? = null,
|
||||||
|
override var mediumStackLimit: Long? = null,
|
||||||
|
) : IItemDefinition, IThingWithDescription by descriptionData
|
@ -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<String, String>
|
|
||||||
get() = descriptionData.racialDescription
|
|
||||||
|
|
||||||
@JsonIgnoreProperty
|
|
||||||
final override val racialShortDescription: Map<String, String>
|
|
||||||
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<InventoryIcon>? by Nullable()
|
|
||||||
final override var itemTags: ImmutableList<String> by NotNull(ImmutableList.of())
|
|
||||||
final override var learnBlueprintsOnPickup: ImmutableList<RegistryReference<IItemDefinition>> 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<String> 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<String> by NotNull(ImmutableList.of())
|
|
||||||
final override var fuelAmount: Long by NotNull(0L)
|
|
||||||
|
|
||||||
final override var pickupSoundsSmall: ImmutableList<String> by NotNull(ImmutableList.of())
|
|
||||||
final override var pickupSoundsMedium: ImmutableList<String> by NotNull(ImmutableList.of())
|
|
||||||
final override var pickupSoundsLarge: ImmutableList<String> by NotNull(ImmutableList.of())
|
|
||||||
final override var smallStackLimit: Long? by Nullable()
|
|
||||||
final override var mediumStackLimit: Long? by Nullable()
|
|
||||||
}
|
|
@ -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<Int, String>,
|
||||||
|
) : IItemDefinition by parent, ILiquidItem {
|
||||||
|
override val itemType: String
|
||||||
|
get() = super<ILiquidItem>.itemType
|
||||||
|
}
|
@ -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<Either<Int, String>>()
|
|
||||||
override var liquid: Either<Int, String> 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Int, String>
|
||||||
|
) : IMaterialItem, IItemDefinition by parent {
|
||||||
|
override val itemType: String
|
||||||
|
get() = super<IMaterialItem>.itemType
|
||||||
|
}
|
@ -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<Either<Int, String>>()
|
|
||||||
override var material: Either<Int, String> 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,23 +12,26 @@ import ru.dbotthepony.kstarbound.defs.RegistryReference
|
|||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
||||||
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class MonsterTypeDefinition(
|
data class MonsterTypeDefinition(
|
||||||
val type: String,
|
val type: String,
|
||||||
override val shortdescription: String,
|
@JsonFlat
|
||||||
override val description: String,
|
val desc: IThingWithDescription,
|
||||||
val categories: ImmutableSet<String> = ImmutableSet.of(),
|
val categories: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
val parts: ImmutableSet<String> = ImmutableSet.of(),
|
val parts: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
val animation: AssetReference<AnimationDefinition>,
|
val animation: AssetReference<AnimationDefinition>,
|
||||||
// [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ],
|
// [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ],
|
||||||
val dropPools: ImmutableList<ImmutableMap<String, RegistryReference<TreasurePoolDefinition>>>,
|
// "dropPools" : [ "smallRobotTreasure" ],
|
||||||
|
val dropPools: Either<ImmutableList<ImmutableMap<String, RegistryReference<TreasurePoolDefinition>>>, ImmutableList<RegistryReference<TreasurePoolDefinition>>>,
|
||||||
val baseParameters: BaseParameters
|
val baseParameters: BaseParameters
|
||||||
) : IThingWithDescription {
|
) : IThingWithDescription by desc {
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class BaseParameters(
|
data class BaseParameters(
|
||||||
val movementSettings: MovementParameters? = null,
|
val movementSettings: MovementParameters? = null,
|
||||||
override val scriptDelta: Int = 1,
|
@JsonFlat
|
||||||
override val scripts: ImmutableList<DirectAssetReference>
|
val script: IScriptable,
|
||||||
) : IScriptable
|
) : IScriptable by script
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.tile
|
package ru.dbotthepony.kstarbound.defs.tile
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
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.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class MaterialModifier(
|
data class MaterialModifier(
|
||||||
@ -23,7 +21,7 @@ data class MaterialModifier(
|
|||||||
val footstepSound: String? = null,
|
val footstepSound: String? = null,
|
||||||
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
|
||||||
@JsonPropertyConfig(isFlat = true)
|
@JsonFlat
|
||||||
val descriptionData: ThingDescription,
|
val descriptionData: ThingDescription,
|
||||||
|
|
||||||
override val renderTemplate: AssetReference<RenderTemplate>,
|
override val renderTemplate: AssetReference<RenderTemplate>,
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.tile
|
package ru.dbotthepony.kstarbound.defs.tile
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
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.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
||||||
import ru.dbotthepony.kstarbound.registerTypeAdapter
|
|
||||||
import ru.dbotthepony.kvector.vector.Color
|
import ru.dbotthepony.kvector.vector.Color
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
@ -26,7 +23,7 @@ data class TileDefinition(
|
|||||||
val health: Double = 0.0,
|
val health: Double = 0.0,
|
||||||
val category: String,
|
val category: String,
|
||||||
|
|
||||||
@JsonPropertyConfig(isFlat = true)
|
@JsonFlat
|
||||||
val descriptionData: ThingDescription,
|
val descriptionData: ThingDescription,
|
||||||
|
|
||||||
override val renderTemplate: AssetReference<RenderTemplate>,
|
override val renderTemplate: AssetReference<RenderTemplate>,
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.util
|
package ru.dbotthepony.kstarbound.defs.util
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов
|
* Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов
|
||||||
*/
|
*/
|
||||||
fun enrollList(input: List<Any>, interner: (String) -> String = String::intern): ImmutableList<Any> {
|
fun enrollList(input: List<Any>, interner: Interner<String> = Interner { it }): ImmutableList<Any> {
|
||||||
val builder = ImmutableList.builder<Any>()
|
val builder = ImmutableList.builder<Any>()
|
||||||
|
|
||||||
for (v in input) {
|
for (v in input) {
|
||||||
when (v) {
|
when (v) {
|
||||||
is Map<*, *> -> builder.add(enrollMap(v as Map<String, Any>, interner))
|
is Map<*, *> -> builder.add(enrollMap(v as Map<String, Any>, interner))
|
||||||
is List<*> -> builder.add(enrollList(v as List<Any>, interner))
|
is List<*> -> builder.add(enrollList(v as List<Any>, 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<Any>, interner: (String) -> String = String::intern):
|
|||||||
/**
|
/**
|
||||||
* Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов
|
* Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов
|
||||||
*/
|
*/
|
||||||
fun enrollMap(input: Map<String, Any>, interner: (String) -> String = String::intern): ImmutableMap<String, Any> {
|
fun enrollMap(input: Map<String, Any>, interner: Interner<String> = Interner { it }): ImmutableMap<String, Any> {
|
||||||
val builder = ImmutableMap.builder<String, Any>()
|
val builder = ImmutableMap.builder<String, Any>()
|
||||||
|
|
||||||
for ((k, v) in input) {
|
for ((k, v) in input) {
|
||||||
when (v) {
|
when (v) {
|
||||||
is Map<*, *> -> builder.put(interner(k), enrollMap(v as Map<String, Any>, interner))
|
is Map<*, *> -> builder.put(interner.intern(k), enrollMap(v as Map<String, Any>, interner))
|
||||||
is List<*> -> builder.put(interner(k), enrollList(v as List<Any>, interner))
|
is List<*> -> builder.put(interner.intern(k), enrollList(v as List<Any>, interner))
|
||||||
else -> builder.put(interner(k), (v as? String)?.let(interner) ?: v)
|
else -> builder.put(interner.intern(k), (v as? String)?.let(interner::intern) ?: v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.util
|
package ru.dbotthepony.kstarbound.defs.util
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
@ -7,17 +8,17 @@ import com.google.gson.JsonObject
|
|||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
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<String> = Interner { it }): Any {
|
||||||
if (input.isNumber) {
|
if (input.isNumber) {
|
||||||
return input.asNumber
|
return input.asNumber
|
||||||
} else if (input.isString) {
|
} else if (input.isString) {
|
||||||
return interner(input.asString)
|
return interner.intern(input.asString)
|
||||||
} else {
|
} else {
|
||||||
return input.asBoolean
|
return input.asBoolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flattenJsonArray(input: JsonArray, interner: (String) -> String = String::intern): ArrayList<Any> {
|
private fun flattenJsonArray(input: JsonArray, interner: Interner<String> = Interner { it }): ArrayList<Any> {
|
||||||
val flattened = ArrayList<Any>(input.size())
|
val flattened = ArrayList<Any>(input.size())
|
||||||
|
|
||||||
for (v in input) {
|
for (v in input) {
|
||||||
@ -32,7 +33,7 @@ private fun flattenJsonArray(input: JsonArray, interner: (String) -> String = St
|
|||||||
return flattened
|
return flattened
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun flattenJsonObject(input: JsonObject, interner: (String) -> String = String::intern): MutableMap<String, Any> {
|
private fun flattenJsonObject(input: JsonObject, interner: Interner<String> = Interner { it }): MutableMap<String, Any> {
|
||||||
val flattened = Object2ObjectOpenHashMap<String, Any>()
|
val flattened = Object2ObjectOpenHashMap<String, Any>()
|
||||||
|
|
||||||
for ((k, v) in input.entrySet()) {
|
for ((k, v) in input.entrySet()) {
|
||||||
@ -46,7 +47,7 @@ private fun flattenJsonObject(input: JsonObject, interner: (String) -> String =
|
|||||||
return flattened
|
return flattened
|
||||||
}
|
}
|
||||||
|
|
||||||
fun flattenJsonElement(input: JsonElement, interner: (String) -> String = String::intern): Any? {
|
fun flattenJsonElement(input: JsonElement, interner: Interner<String> = Interner { it }): Any? {
|
||||||
return when (input) {
|
return when (input) {
|
||||||
is JsonObject -> flattenJsonObject(input, interner)
|
is JsonObject -> flattenJsonObject(input, interner)
|
||||||
is JsonArray -> flattenJsonArray(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: JsonObject, interner: Interner<String> = Interner { it }) = flattenJsonObject(input, interner)
|
||||||
fun flattenJsonElement(input: JsonArray, interner: (String) -> String = String::intern) = flattenJsonArray(input, interner)
|
fun flattenJsonElement(input: JsonArray, interner: Interner<String> = Interner { it }) = flattenJsonArray(input, interner)
|
||||||
fun flattenJsonElement(input: JsonPrimitive, interner: (String) -> String = String::intern) = flattenJsonPrimitive(input, interner)
|
fun flattenJsonElement(input: JsonPrimitive, interner: Interner<String> = Interner { it }) = flattenJsonPrimitive(input, interner)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.io.json
|
package ru.dbotthepony.kstarbound.io.json
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.internal.bind.JsonTreeReader
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
@ -24,6 +26,7 @@ object EitherTypeAdapter : TypeAdapterFactory {
|
|||||||
return object : TypeAdapter<Either<Any, Any>>() {
|
return object : TypeAdapter<Either<Any, Any>>() {
|
||||||
private val leftAdapter = gson.getAdapter(TypeToken.get(left)) as TypeAdapter<Any?>
|
private val leftAdapter = gson.getAdapter(TypeToken.get(left)) as TypeAdapter<Any?>
|
||||||
private val rightAdapter = gson.getAdapter(TypeToken.get(right)) as TypeAdapter<Any?>
|
private val rightAdapter = gson.getAdapter(TypeToken.get(right)) as TypeAdapter<Any?>
|
||||||
|
private val elemAdapter = gson.getAdapter(JsonElement::class.java)
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: Either<Any, Any>?) {
|
override fun write(out: JsonWriter, value: Either<Any, Any>?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
@ -36,11 +39,13 @@ object EitherTypeAdapter : TypeAdapterFactory {
|
|||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.peek() == JsonToken.NULL)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
val elem = elemAdapter.read(`in`)
|
||||||
|
|
||||||
return try {
|
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) {
|
} catch(leftError: Throwable) {
|
||||||
try {
|
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) {
|
} catch(rightError: Throwable) {
|
||||||
val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)")
|
val error = JsonSyntaxException("Can't read Either of values (left is $left, right is $right)")
|
||||||
error.addSuppressed(leftError)
|
error.addSuppressed(leftError)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.io.json
|
package ru.dbotthepony.kstarbound.io.json
|
||||||
|
|
||||||
import com.google.gson.Gson
|
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.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
@ -93,3 +98,44 @@ fun JsonReader.consumeNull(): Boolean {
|
|||||||
|
|
||||||
return false
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,79 +6,54 @@ import com.google.gson.TypeAdapterFactory
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlin.reflect.KClass
|
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<String> = [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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]
|
* Заставляет указанное свойство быть проигнорированным при автоматическом создании [BuilderAdapter]
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class JsonIgnoreProperty
|
annotation class JsonIgnore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Выставляет флаги данному свойству при автоматическом создании [BuilderAdapter]
|
* Указывает, что данное свойство или аргумент является компонующим, а не потомственным значением.
|
||||||
*
|
*
|
||||||
* @see BuilderAdapter.Builder.add
|
* Заставляет адаптеры распаковать данное свойство или аргумент на том же уровне, что и родителя
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class JsonPropertyConfig(
|
annotation class JsonFlat
|
||||||
val isFlat: Boolean = false,
|
|
||||||
|
|
||||||
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]
|
* Указывает, что для данного класса можно автоматически создать [FactoryAdapter]
|
||||||
*
|
*
|
||||||
* В подавляющем большинстве случаев это работает исключительно с data классами
|
* Чаще всего, это используется только для data классов
|
||||||
*
|
*
|
||||||
* С технической точки зрения, у класса определяются все свойства и происходит поиск *главного* конструктора, а
|
* @see JsonIgnore
|
||||||
* затем аргументы конструктора отражаются на свойства класса
|
|
||||||
*
|
|
||||||
* @see JsonIgnoreProperty
|
|
||||||
* @see JsonPropertyConfig
|
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@ -88,11 +63,6 @@ annotation class JsonFactory(
|
|||||||
*/
|
*/
|
||||||
val storesJson: Boolean = false,
|
val storesJson: Boolean = false,
|
||||||
|
|
||||||
/**
|
|
||||||
* @see FactoryAdapter.Builder.logMisses
|
|
||||||
*/
|
|
||||||
val logMisses: Boolean = true,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see FactoryAdapter.Builder.inputAsList
|
* @see FactoryAdapter.Builder.inputAsList
|
||||||
* @see FactoryAdapter.Builder.inputAsMap
|
* @see FactoryAdapter.Builder.inputAsMap
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.io.json.builder
|
package ru.dbotthepony.kstarbound.io.json.builder
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.common.collect.ImmutableSet
|
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonObject
|
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.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
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.ObjectLinkedOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.FreezableDefintionBuilder
|
|
||||||
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter.Builder
|
|
||||||
import ru.dbotthepony.kstarbound.util.INotNullDelegate
|
import ru.dbotthepony.kstarbound.util.INotNullDelegate
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -43,21 +39,6 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
*/
|
*/
|
||||||
val properties: ImmutableMap<String, IResolvedMutableProperty<T, *>>,
|
val properties: ImmutableMap<String, IResolvedMutableProperty<T, *>>,
|
||||||
|
|
||||||
/**
|
|
||||||
* Ключи, которые необходимо игнорировать при чтении JSON
|
|
||||||
*/
|
|
||||||
val ignoreKeys: ImmutableSet<String>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Builder.extraPropertiesAreFatal
|
|
||||||
*/
|
|
||||||
val extraPropertiesAreFatal: Boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Builder.logMisses
|
|
||||||
*/
|
|
||||||
val logMisses: Boolean,
|
|
||||||
|
|
||||||
val stringInterner: Interner<String> = Interner { it },
|
val stringInterner: Interner<String> = Interner { it },
|
||||||
) : TypeAdapter<T>() {
|
) : TypeAdapter<T>() {
|
||||||
private val loggedMisses = ObjectOpenHashSet<String>()
|
private val loggedMisses = ObjectOpenHashSet<String>()
|
||||||
@ -92,21 +73,11 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// загружаем указатели на стек
|
// загружаем указатели на стек
|
||||||
val logMisses = logMisses
|
|
||||||
val extraPropertiesAreFatal = extraPropertiesAreFatal
|
|
||||||
val loggedMisses = loggedMisses
|
|
||||||
val ignoreKeys = ignoreKeys
|
|
||||||
|
|
||||||
reader.beginObject()
|
reader.beginObject()
|
||||||
|
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
val name = reader.nextName()
|
val name = reader.nextName()
|
||||||
|
|
||||||
if (ignoreKeys.contains(name)) {
|
|
||||||
reader.skipValue()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val property = properties[name]
|
val property = properties[name]
|
||||||
|
|
||||||
if (property != null) {
|
if (property != null) {
|
||||||
@ -131,11 +102,7 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
throw JsonSyntaxException("Reading property ${property.name} of ${instance::class.qualifiedName} near ${reader.path}", err)
|
throw JsonSyntaxException("Reading property ${property.name} of ${instance::class.qualifiedName} near ${reader.path}", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (extraPropertiesAreFatal) {
|
if (loggedMisses.add(name)) {
|
||||||
throw JsonSyntaxException("$name is not a valid property of ${instance::class.qualifiedName}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logMisses && loggedMisses.add(name)) {
|
|
||||||
LOGGER.warn("${instance::class.qualifiedName} has no property for storing $name")
|
LOGGER.warn("${instance::class.qualifiedName} has no property for storing $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,25 +153,18 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance is FreezableDefintionBuilder) {
|
|
||||||
instance.freeze()
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapterFactory {
|
class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapterFactory {
|
||||||
private val properties = ArrayList<IResolvableMutableProperty<T, *>>()
|
private val properties = ArrayList<IResolvableMutableProperty<T, *>>()
|
||||||
private val ignoreKeys = ObjectArraySet<String>()
|
|
||||||
var extraPropertiesAreFatal = false
|
|
||||||
var logMisses: Boolean? = null
|
|
||||||
private val factoryReturnType by lazy { factory.invoke()::class.java }
|
private val factoryReturnType by lazy { factory.invoke()::class.java }
|
||||||
|
|
||||||
var stringInterner: Interner<String> = Interner { it }
|
var stringInterner: Interner<String> = Interner { it }
|
||||||
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == factoryReturnType) {
|
if (type.rawType == factoryReturnType)
|
||||||
return build(gson) as TypeAdapter<T>
|
return build(gson) as TypeAdapter<T>
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -219,102 +179,18 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
factory = factory,
|
factory = factory,
|
||||||
properties = map.build(),
|
properties = map.build(),
|
||||||
stringInterner = stringInterner,
|
stringInterner = stringInterner,
|
||||||
ignoreKeys = ImmutableSet.copyOf(ignoreKeys),
|
|
||||||
extraPropertiesAreFatal = extraPropertiesAreFatal,
|
|
||||||
logMisses = logMisses ?: properties.none { it.isFlat },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Используйте как TypeAdapterFactory")
|
|
||||||
fun build(): BuilderAdapter<T> {
|
|
||||||
val map = ImmutableMap.Builder<String, IResolvedMutableProperty<T, *>>()
|
|
||||||
|
|
||||||
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<T> {
|
|
||||||
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<T> {
|
|
||||||
logMisses = flag
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
for (field in fields)
|
for (field in fields) add(field)
|
||||||
auto(field)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun <V> add(property: KMutableProperty1<T, V>, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder<T> {
|
||||||
* Добавляет указанное свойство в будущий адаптер с указанным [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 <V> add(property: KMutableProperty1<T, V>, adapter: TypeAdapter<V>, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder<T> {
|
|
||||||
if (properties.any { it.property == property }) {
|
if (properties.any { it.property == property }) {
|
||||||
throw IllegalArgumentException("Property $property is defined twice")
|
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 <V> auto(property: KMutableProperty1<T, V>, isFlat: Boolean = false, mustBePresent: Boolean? = null): Builder<T> {
|
|
||||||
if (properties.any { it.property == property }) {
|
|
||||||
throw IllegalArgumentException("Property $property is defined twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreKeys.remove(property.name)
|
|
||||||
properties.add(ResolvableMutableProperty(
|
properties.add(ResolvableMutableProperty(
|
||||||
property = property,
|
property = property,
|
||||||
mustBePresent = mustBePresent,
|
mustBePresent = mustBePresent,
|
||||||
@ -323,14 +199,35 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ignoreKey(name: String): Builder<T> {
|
class Factory(val stringInterner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
||||||
if (properties.any { it.property.name == name }) {
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
throw IllegalArgumentException("Can not ignore key $name because we have property with this name!")
|
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<String, KMutableProperty1<*, *>>()
|
||||||
|
collectDecl(kclass, declarations)
|
||||||
|
|
||||||
|
for (decl in declarations.values) {
|
||||||
|
if (decl.annotations.none { it is JsonIgnore }) {
|
||||||
|
builder.add(
|
||||||
|
decl as KMutableProperty1<T, *>,
|
||||||
|
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 null
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,47 +270,4 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(val stringInterner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
|
||||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
|
||||||
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<String, KMutableProperty1<*, *>>()
|
|
||||||
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<T, *>)
|
|
||||||
} else {
|
|
||||||
config as JsonPropertyConfig
|
|
||||||
builder.auto(decl as KMutableProperty1<T, *>, isFlat = config.isFlat, mustBePresent = config.realMustBePresent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build(gson)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap
|
|||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParseException
|
import com.google.gson.JsonParseException
|
||||||
import com.google.gson.JsonSyntaxException
|
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.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
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.Object2IntArrayMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.util.enrollList
|
import ru.dbotthepony.kstarbound.defs.util.enrollList
|
||||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||||
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
||||||
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
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 java.lang.reflect.Constructor
|
||||||
import kotlin.jvm.internal.DefaultConstructorMarker
|
import kotlin.jvm.internal.DefaultConstructorMarker
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
@ -37,28 +40,38 @@ import kotlin.reflect.full.primaryConstructor
|
|||||||
* [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе.
|
* [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе.
|
||||||
*/
|
*/
|
||||||
class FactoryAdapter<T : Any> private constructor(
|
class FactoryAdapter<T : Any> private constructor(
|
||||||
val bound: KClass<T>,
|
val clazz: KClass<T>,
|
||||||
val types: ImmutableList<IResolvedProperty<T, *>>,
|
val types: ImmutableList<IResolvedProperty<T, *>>,
|
||||||
|
aliases: Map<String, String>,
|
||||||
val asJsonArray: Boolean,
|
val asJsonArray: Boolean,
|
||||||
val storesJson: Boolean,
|
val storesJson: Boolean,
|
||||||
val logMisses: Boolean,
|
|
||||||
val stringInterner: Interner<String>
|
val stringInterner: Interner<String>
|
||||||
) : TypeAdapter<T>() {
|
) : TypeAdapter<T>() {
|
||||||
private val name2index = Object2IntArrayMap<String>()
|
private val name2index = Object2IntArrayMap<String>()
|
||||||
private val loggedMisses = ObjectArraySet<String>()
|
private val loggedMisses = ObjectArraySet<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (asJsonArray && types.any { it.isFlat }) {
|
||||||
|
throw IllegalArgumentException("Can't have both flat properties and input be as json array")
|
||||||
|
}
|
||||||
|
|
||||||
name2index.defaultReturnValue(-1)
|
name2index.defaultReturnValue(-1)
|
||||||
|
|
||||||
for ((i, pair) in types.withIndex()) {
|
for ((i, pair) in types.withIndex()) {
|
||||||
name2index[pair.property.name] = i
|
name2index[pair.property.name] = i
|
||||||
|
|
||||||
|
aliases.entries.forEach {
|
||||||
|
if (it.value == pair.property.name) {
|
||||||
|
name2index[it.key] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обычный конструктор класса (без флагов "значения по умолчанию")
|
* Обычный конструктор класса (без флагов "значения по умолчанию")
|
||||||
*/
|
*/
|
||||||
private val regularFactory: KFunction<T> = bound.constructors.firstOrNull first@{
|
private val regularFactory: KFunction<T> = clazz.constructors.firstOrNull first@{
|
||||||
var requiredSize = types.size
|
var requiredSize = types.size
|
||||||
|
|
||||||
if (storesJson)
|
if (storesJson)
|
||||||
@ -98,7 +111,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return@first false
|
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'ном, для создания классов со значениями по умолчанию
|
* Синтетический конструктор класса, который создаётся Kotlin'ном, для создания классов со значениями по умолчанию
|
||||||
@ -117,19 +130,15 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
typelist.add(DefaultConstructorMarker::class.java)
|
typelist.add(DefaultConstructorMarker::class.java)
|
||||||
|
|
||||||
bound.java.getDeclaredConstructor(*typelist.toTypedArray())
|
clazz.java.getDeclaredConstructor(*typelist.toTypedArray())
|
||||||
} catch(_: NoSuchMethodException) {
|
} catch(_: NoSuchMethodException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val syntheticPrimitives: Array<Any?>?
|
private val syntheticPrimitives = Int2ObjectOpenHashMap<Any>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (syntheticFactory == null) {
|
if (syntheticFactory != null) {
|
||||||
syntheticPrimitives = null
|
|
||||||
} else {
|
|
||||||
syntheticPrimitives = arrayOfNulls(syntheticFactory.parameters.size)
|
|
||||||
|
|
||||||
for ((i, param) in syntheticFactory.parameters.withIndex()) {
|
for ((i, param) in syntheticFactory.parameters.withIndex()) {
|
||||||
val type = param.parameterizedType as? Class<*> ?: continue
|
val type = param.parameterizedType as? Class<*> ?: continue
|
||||||
|
|
||||||
@ -158,10 +167,24 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
out.beginObject()
|
out.beginObject()
|
||||||
|
|
||||||
for ((field, adapter) in types) {
|
for (type in types) {
|
||||||
out.name(field.name)
|
if (type.isFlat) {
|
||||||
@Suppress("unchecked_cast")
|
val (field, adapter) = type
|
||||||
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
|
val result = (adapter as TypeAdapter<Any>).toJsonTree((field as KProperty1<T, Any>).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<Any>).write(out, (field as KProperty1<T, Any>).get(value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.endObject()
|
out.endObject()
|
||||||
@ -173,7 +196,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
// таблица присутствия значений (если значение true то на i было значение внутри json)
|
// таблица присутствия значений (если значение true то на i было значение внутри json)
|
||||||
val presentValues = BooleanArray(types.size + (if (storesJson) 1 else 0))
|
val presentValues = BooleanArray(types.size + (if (storesJson) 1 else 0))
|
||||||
val readValues = arrayOfNulls<Any>(types.size + (if (storesJson) 1 else 0))
|
var readValues = arrayOfNulls<Any>(types.size + (if (storesJson) 1 else 0))
|
||||||
|
|
||||||
if (storesJson)
|
if (storesJson)
|
||||||
presentValues[presentValues.size - 1] = true
|
presentValues[presentValues.size - 1] = true
|
||||||
@ -183,28 +206,26 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
// Если нам необходимо читать объект как набор данных массива, то давай
|
// Если нам необходимо читать объект как набор данных массива, то давай
|
||||||
if (asJsonArray) {
|
if (asJsonArray) {
|
||||||
val iterator = types.iterator()
|
|
||||||
var fieldId = 0
|
|
||||||
|
|
||||||
if (storesJson) {
|
if (storesJson) {
|
||||||
val readArray = TypeAdapters.JSON_ELEMENT.read(reader)
|
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")
|
throw JsonParseException("Expected JSON element to be an Array, ${readArray::class.qualifiedName} given")
|
||||||
}
|
|
||||||
|
|
||||||
reader = JsonTreeReader(readArray)
|
reader = JsonTreeReader(readArray)
|
||||||
readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List<Any>, stringInterner::intern)
|
readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray, stringInterner) as List<Any>, stringInterner)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
|
val iterator = types.iterator()
|
||||||
|
var fieldId = 0
|
||||||
|
|
||||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
val name = fieldId.toString()
|
val name = fieldId.toString()
|
||||||
|
|
||||||
if (!storesJson && loggedMisses.add(name)) {
|
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()
|
reader.skipValue()
|
||||||
@ -213,14 +234,13 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val tuple = iterator.next()
|
val (field, adapter) = iterator.next()
|
||||||
val (field, adapter) = tuple
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
readValues[fieldId] = adapter.read(reader)
|
readValues[fieldId] = adapter.read(reader)
|
||||||
presentValues[fieldId] = true
|
presentValues[fieldId] = true
|
||||||
} catch(err: Throwable) {
|
} 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++
|
fieldId++
|
||||||
@ -228,18 +248,18 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
// иначе - читаем как json object
|
// иначе - читаем как json object
|
||||||
} else {
|
} else {
|
||||||
var json: JsonObject by Delegates.notNull()
|
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)
|
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")
|
throw JsonParseException("Expected JSON element to be a Map, ${readMap::class.qualifiedName} given")
|
||||||
}
|
|
||||||
|
|
||||||
json = readMap
|
json = readMap
|
||||||
reader = JsonTreeReader(readMap)
|
reader = JsonTreeReader(readMap)
|
||||||
readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map<String, Any>, stringInterner::intern)
|
|
||||||
|
if (storesJson)
|
||||||
|
readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap, stringInterner) as Map<String, Any>, stringInterner)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.beginObject()
|
reader.beginObject()
|
||||||
@ -249,8 +269,8 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
val fieldId = name2index.getInt(name)
|
val fieldId = name2index.getInt(name)
|
||||||
|
|
||||||
if (fieldId == -1) {
|
if (fieldId == -1) {
|
||||||
if (!storesJson && !hasFlatValues && logMisses && loggedMisses.add(name)) {
|
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()
|
reader.skipValue()
|
||||||
@ -268,7 +288,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
readValues[fieldId] = adapter.read(reader)
|
readValues[fieldId] = adapter.read(reader)
|
||||||
presentValues[fieldId] = true
|
presentValues[fieldId] = true
|
||||||
} catch(err: Throwable) {
|
} 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<T : Any> private constructor(
|
|||||||
readValues[i] = read
|
readValues[i] = read
|
||||||
}
|
}
|
||||||
} catch(err: Throwable) {
|
} 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<T : Any> private constructor(
|
|||||||
|
|
||||||
if (readValues[i] == null) {
|
if (readValues[i] == null) {
|
||||||
if (!tuple.isMarkedNullable) {
|
if (!tuple.isMarkedNullable) {
|
||||||
throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls")
|
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")
|
||||||
if (!regularFactory.parameters[i].isOptional && !presentValues[i]) {
|
|
||||||
throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} must be defined (even just as null)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,86 +334,59 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
return regularFactory.call(*readValues as Array<out Any>)
|
return regularFactory.call(*readValues as Array<out Any>)
|
||||||
// иначе - в бой вступает синтетический конструктор, с флагами "значения по умолчанию"
|
// иначе - в бой вступает синтетический конструктор, с флагами "значения по умолчанию"
|
||||||
} else {
|
} else {
|
||||||
// количество bitflag'ов значений по умолчанию
|
var argumentFlagCount = presentValues.size / 31 + 1 /* DefaultConstructorMarker */
|
||||||
val ints = if (presentValues.size % 31 == 0) presentValues.size / 31 else presentValues.size / 31 + 1
|
if (presentValues.size % 31 != 0) argumentFlagCount++
|
||||||
// максимум считанных значений + bitflag аргументы значений по умолчанию + null типа DefaultConstructorMarker
|
readValues = readValues.copyOf(readValues.size + argumentFlagCount)
|
||||||
val copied = readValues.copyOf(readValues.size + ints + 1)
|
|
||||||
var intIndex = readValues.size
|
|
||||||
var target = 0
|
|
||||||
var targetMove = 0
|
|
||||||
|
|
||||||
for (bool in presentValues) {
|
var flagIndex = readValues.size - argumentFlagCount
|
||||||
if (!bool) {
|
var flags = 0
|
||||||
target = target.or(1.shl(targetMove))
|
var flagBit = 0
|
||||||
}
|
|
||||||
|
|
||||||
targetMove++
|
for (isPresent in presentValues) {
|
||||||
|
if (!isPresent) flags = flags.or(1.shl(flagBit))
|
||||||
|
flagBit++
|
||||||
|
|
||||||
if (targetMove >= 32) {
|
if (flagBit >= 32) {
|
||||||
copied[intIndex++] = target
|
readValues[flagIndex++] = flags
|
||||||
target = 0
|
flags = 0
|
||||||
targetMove = 0
|
flagBit = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetMove != 0) {
|
if (flagBit != 0) {
|
||||||
copied[intIndex] = target
|
readValues[flagIndex] = flags
|
||||||
}
|
}
|
||||||
|
|
||||||
val syntheticPrimitives = syntheticPrimitives!!
|
for ((i, field) in types.withIndex()) {
|
||||||
|
if (readValues[i] != null) continue
|
||||||
for ((i, tuple) in types.withIndex()) {
|
|
||||||
if (copied[i] != null) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val (field) = tuple
|
|
||||||
val param = regularFactory.parameters[i]
|
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]) {
|
if (param.isOptional && !presentValues[i]) {
|
||||||
copied[i] = syntheticPrimitives[i]
|
readValues[i] = syntheticPrimitives[i]
|
||||||
continue
|
} 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] на основе заданных параметров
|
* Позволяет построить класс [FactoryAdapter] на основе заданных параметров
|
||||||
*/
|
*/
|
||||||
class Builder<T : Any>(val clazz: KClass<T>) : TypeAdapterFactory {
|
class Builder<T : Any>(val clazz: KClass<T>, vararg fields: KProperty1<T, *>) : TypeAdapterFactory {
|
||||||
constructor(clazz: KClass<T>, vararg fields: KProperty1<T, *>) : this(clazz) {
|
private var asList = false
|
||||||
for (field in fields) {
|
|
||||||
auto(field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var storesJson = false
|
private var storesJson = false
|
||||||
private var logMisses = true
|
|
||||||
private val types = ArrayList<IResolvableProperty<T, *>>()
|
private val types = ArrayList<IResolvableProperty<T, *>>()
|
||||||
private var stringTransformer: ((String) -> T)? = null
|
private val aliases = Object2ObjectArrayMap<String, String>()
|
||||||
var stringInterner: Interner<String> = Interner { it }
|
var stringInterner: Interner<String> = Interner { it }
|
||||||
|
|
||||||
fun stringInterner(interner: Interner<String>): Builder<T> {
|
init {
|
||||||
this.stringInterner = interner
|
for (field in fields) {
|
||||||
return this
|
add(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ifString(transformer: (String) -> T): Builder<T> {
|
|
||||||
stringTransformer = transformer
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
@ -413,44 +404,13 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" }
|
check(!asList || types.none { it.isFlat }) { "Can't have both flat properties and json data array layout" }
|
||||||
|
|
||||||
return FactoryAdapter(
|
return FactoryAdapter(
|
||||||
bound = clazz,
|
clazz = clazz,
|
||||||
types = ImmutableList.copyOf(types.map { it.resolve(gson) }),
|
types = ImmutableList.copyOf(types.map { it.resolve(gson) }),
|
||||||
asJsonArray = asList,
|
asJsonArray = asList,
|
||||||
storesJson = storesJson,
|
storesJson = storesJson,
|
||||||
logMisses = logMisses,
|
|
||||||
stringInterner = stringInterner,
|
stringInterner = stringInterner,
|
||||||
).let {
|
aliases = aliases
|
||||||
if (stringTransformer != null)
|
)
|
||||||
it.ifString(stringTransformer!!)
|
|
||||||
else
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Собирает этот [FactoryAdapter] без GSON объекта
|
|
||||||
*
|
|
||||||
* Не рекомендуется использовать, лучше всего использовать как [TypeAdapterFactory]
|
|
||||||
*
|
|
||||||
* Несмотря на @Deprecated, данный вариант метода удалён не будет
|
|
||||||
*/
|
|
||||||
@Deprecated("Используйте как TypeAdapterFactory")
|
|
||||||
fun build(): TypeAdapter<T> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,34 +426,15 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun <V> add(field: KProperty1<T, V>, isFlat: Boolean = false, transform: (TypeAdapter<V>) -> TypeAdapter<V> = { it }): Builder<T> {
|
||||||
* Логировать ли json значения которые нет в списке свойств
|
|
||||||
*/
|
|
||||||
fun logMisses(flag: Boolean = true): Builder<T> {
|
|
||||||
logMisses = flag
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Добавляет свойство с определённым [adapter]
|
|
||||||
*/
|
|
||||||
fun <V> add(field: KProperty1<T, V>, adapter: TypeAdapter<V>, isFlat: Boolean = false): Builder<T> {
|
|
||||||
types.add(ResolvedProperty(field, adapter, isFlat = isFlat))
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Автоматически определяет необходимый адаптер типа к свойству при сборке данного адаптера внутри Gson
|
|
||||||
*
|
|
||||||
* Можно указать [transform] для изменения определённого адаптера
|
|
||||||
*/
|
|
||||||
@Suppress("unchecked_cast")
|
|
||||||
fun <V> auto(field: KProperty1<T, V>, isFlat: Boolean = false, transform: (TypeAdapter<V>) -> TypeAdapter<V> = { it }): Builder<T> {
|
|
||||||
types.add(ResolvableProperty(field, isFlat = isFlat, transform = transform))
|
types.add(ResolvableProperty(field, isFlat = isFlat, transform = transform))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private var asList = false
|
fun alias(alias: String, canonical: String): Builder<T> {
|
||||||
|
aliases[alias] = canonical
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* При выставлении данного флага в качестве исходной структуры будет приниматься Json объект:
|
* При выставлении данного флага в качестве исходной структуры будет приниматься Json объект:
|
||||||
@ -542,7 +483,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder.storesJson(config.storesJson)
|
builder.storesJson(config.storesJson)
|
||||||
builder.logMisses(config.logMisses)
|
|
||||||
builder.stringInterner = stringInterner
|
builder.stringInterner = stringInterner
|
||||||
|
|
||||||
if (properties.isEmpty()) {
|
if (properties.isEmpty()) {
|
||||||
@ -551,20 +491,20 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}")
|
val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}")
|
||||||
|
|
||||||
if (!config.storesJson) {
|
val params = foundConstructor.parameters
|
||||||
for (argument in foundConstructor.parameters) {
|
val lastIndex = if (config.storesJson) params.size - 1 else params.size
|
||||||
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
|
|
||||||
|
|
||||||
for (i in 0 until params.size - 1) {
|
for (i in 0 until lastIndex) {
|
||||||
val argument = params[i]
|
val argument = params[i]
|
||||||
val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) }
|
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.add(property, isFlat = property.annotations.any { it.annotationClass == JsonFlat::class })
|
||||||
builder.auto(property, isFlat = config?.isFlat ?: false)
|
|
||||||
|
property.annotations.firstOrNull { it.annotationClass == JsonAlias::class }?.let {
|
||||||
|
it as JsonAlias
|
||||||
|
|
||||||
|
for (name in it.aliases) {
|
||||||
|
builder.alias(name, property.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user