Создание BuilderAdapter через аннотации
This commit is contained in:
parent
38d341913a
commit
20bd844f23
@ -46,6 +46,7 @@ import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
||||
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
@ -159,6 +160,9 @@ object Starbound {
|
||||
// все enum'ы без особых настроек
|
||||
.registerTypeAdapterFactory(EnumAdapter.Companion)
|
||||
|
||||
// автоматическое создание BuilderAdapter
|
||||
.registerTypeAdapterFactory(BuilderAdapter.Companion)
|
||||
|
||||
.also(::addStarboundJsonAdapters)
|
||||
|
||||
.create()
|
||||
|
@ -93,11 +93,6 @@ fun addStarboundJsonAdapters(builder: GsonBuilder) {
|
||||
registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER)
|
||||
|
||||
// Предметы
|
||||
registerTypeAdapterFactory(ItemPrototype.ADAPTER)
|
||||
registerTypeAdapterFactory(CurrencyItemPrototype.ADAPTER)
|
||||
registerTypeAdapterFactory(ArmorItemPrototype.ADAPTER)
|
||||
registerTypeAdapterFactory(MaterialItemPrototype.ADAPTER)
|
||||
registerTypeAdapterFactory(LiquidItemPrototype.ADAPTER)
|
||||
registerTypeAdapterFactory(IItemDefinition.InventoryIcon.ADAPTER)
|
||||
registerTypeAdapterFactory(IFossilItemDefinition.FossilSetDescription.ADAPTER)
|
||||
registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.ADAPTER)
|
||||
|
@ -4,11 +4,14 @@ import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonIgnoreProperty
|
||||
import ru.dbotthepony.kstarbound.io.json.util.asJsonObject
|
||||
import ru.dbotthepony.kstarbound.io.json.util.asList
|
||||
import ru.dbotthepony.kstarbound.io.json.neverNull
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
|
||||
@JsonBuilder
|
||||
class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition {
|
||||
override var colorOptions: ImmutableList<Map<String, String>> = ImmutableList.of()
|
||||
override var maleFrames: IArmorItemDefinition.ArmorFrames by NotNullVar()
|
||||
@ -19,6 +22,7 @@ class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition {
|
||||
override var scripts: ImmutableList<String> = ImmutableList.of()
|
||||
override var scriptDelta: Int = 1
|
||||
|
||||
@JsonIgnoreProperty
|
||||
override var armorType: ArmorPieceType by NotNullVar()
|
||||
|
||||
init {
|
||||
@ -57,16 +61,4 @@ class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition {
|
||||
armorType = armorType,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = BuilderAdapter.Builder(::ArmorItemPrototype)
|
||||
.also { addFields(it as BuilderAdapter.Builder<ItemPrototype>) } // безопасность: свойства родительского класса объявлены как final
|
||||
.auto(ArmorItemPrototype::colorOptions)
|
||||
.auto(ArmorItemPrototype::maleFrames)
|
||||
.auto(ArmorItemPrototype::femaleFrames)
|
||||
.auto(ArmorItemPrototype::level)
|
||||
.auto(ArmorItemPrototype::leveledStatusEffects)
|
||||
.auto(ArmorItemPrototype::scripts)
|
||||
.auto(ArmorItemPrototype::scriptDelta)
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ package ru.dbotthepony.kstarbound.defs.item
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
|
||||
@JsonBuilder
|
||||
class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition {
|
||||
override var pickupSoundsSmall: ImmutableList<String> = ImmutableList.of()
|
||||
override var pickupSoundsMedium: ImmutableList<String> = ImmutableList.of()
|
||||
@ -48,16 +50,4 @@ class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition {
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = BuilderAdapter.Builder(::CurrencyItemPrototype)
|
||||
.also { addFields(it as BuilderAdapter.Builder<ItemPrototype>) } // безопасность: свойства родительского класса объявлены как final
|
||||
.auto(CurrencyItemPrototype::pickupSoundsSmall)
|
||||
.auto(CurrencyItemPrototype::pickupSoundsMedium)
|
||||
.auto(CurrencyItemPrototype::pickupSoundsLarge)
|
||||
.auto(CurrencyItemPrototype::smallStackLimit)
|
||||
.auto(CurrencyItemPrototype::mediumStackLimit)
|
||||
.auto(CurrencyItemPrototype::currency)
|
||||
.auto(CurrencyItemPrototype::value)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,18 @@ import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.INativeJsonHolder
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonIgnoreProperty
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
|
||||
@JsonBuilder
|
||||
open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
@JsonIgnoreProperty
|
||||
final override var shortdescription: String = "..."
|
||||
@JsonIgnoreProperty
|
||||
final override var description: String = "..."
|
||||
|
||||
final override var itemName: String by NotNullVar()
|
||||
final override var price: Long = 0L
|
||||
final override var rarity: ItemRarity = ItemRarity.COMMON
|
||||
@ -26,8 +33,10 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
final override var radioMessagesOnPickup: ImmutableList<String> = ImmutableList.of()
|
||||
final override var fuelAmount: Long? = null
|
||||
|
||||
@JsonPropertyConfig(isFlat = true)
|
||||
var descriptionData: ThingDescription by NotNullVar()
|
||||
|
||||
@JsonIgnoreProperty
|
||||
var json: Map<String, Any> = mapOf()
|
||||
|
||||
final override fun acceptJson(json: MutableMap<String, Any>) {
|
||||
@ -56,32 +65,4 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
json = enrollMap(json),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = BuilderAdapter.Builder(::ItemPrototype)
|
||||
.also(::addFields)
|
||||
|
||||
fun addFields(builder: BuilderAdapter.Builder<ItemPrototype>) {
|
||||
with(builder) {
|
||||
auto(ItemPrototype::shortdescription)
|
||||
auto(ItemPrototype::description)
|
||||
auto(ItemPrototype::itemName)
|
||||
auto(ItemPrototype::price)
|
||||
auto(ItemPrototype::rarity)
|
||||
auto(ItemPrototype::category)
|
||||
auto(ItemPrototype::inventoryIcon)
|
||||
auto(ItemPrototype::itemTags)
|
||||
auto(ItemPrototype::learnBlueprintsOnPickup)
|
||||
auto(ItemPrototype::maxStack)
|
||||
auto(ItemPrototype::eventCategory)
|
||||
auto(ItemPrototype::consumeOnPickup)
|
||||
auto(ItemPrototype::pickupQuestTemplates)
|
||||
auto(ItemPrototype::tooltipKind)
|
||||
auto(ItemPrototype::twoHanded)
|
||||
auto(ItemPrototype::radioMessagesOnPickup)
|
||||
auto(ItemPrototype::fuelAmount)
|
||||
auto(ItemPrototype::descriptionData, isFlat = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package ru.dbotthepony.kstarbound.defs.item
|
||||
import ru.dbotthepony.kstarbound.defs.MaterialReference
|
||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder
|
||||
|
||||
@JsonBuilder
|
||||
class LiquidItemPrototype : ItemPrototype() {
|
||||
var liquid: MaterialReference? = null
|
||||
|
||||
@ -42,12 +44,4 @@ class LiquidItemPrototype : ItemPrototype() {
|
||||
json = enrollMap(json),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = BuilderAdapter.Builder(::LiquidItemPrototype)
|
||||
.also { addFields(it as BuilderAdapter.Builder<ItemPrototype>) } // безопасность: свойства родительского класса объявлены как final
|
||||
.auto(LiquidItemPrototype::liquid)
|
||||
.auto(LiquidItemPrototype::liquidId)
|
||||
.auto(LiquidItemPrototype::liquidName)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package ru.dbotthepony.kstarbound.defs.item
|
||||
import ru.dbotthepony.kstarbound.defs.MaterialReference
|
||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder
|
||||
|
||||
@JsonBuilder
|
||||
class MaterialItemPrototype : ItemPrototype() {
|
||||
var material: MaterialReference? = null
|
||||
|
||||
@ -42,12 +44,4 @@ class MaterialItemPrototype : ItemPrototype() {
|
||||
json = enrollMap(json),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = BuilderAdapter.Builder(::MaterialItemPrototype)
|
||||
.also { addFields(it as BuilderAdapter.Builder<ItemPrototype>) } // безопасность: свойства родительского класса объявлены как final
|
||||
.auto(MaterialItemPrototype::material)
|
||||
.auto(MaterialItemPrototype::materialId)
|
||||
.auto(MaterialItemPrototype::materialName)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
package ru.dbotthepony.kstarbound.io.json.builder
|
||||
|
||||
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,
|
||||
|
||||
val includeSuperclassProperties: Boolean = true,
|
||||
)
|
||||
|
||||
val JsonBuilder.realLogMisses get() = logMisses.toBool()
|
||||
|
||||
/**
|
||||
* Заставляет указанное свойство быть проигнорированным при автоматическом создании [BuilderAdapter]
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class JsonIgnoreProperty
|
||||
|
||||
/**
|
||||
* Выставляет флаги данному свойству при автоматическом создании [BuilderAdapter]
|
||||
*
|
||||
* @see BuilderAdapter.Builder.add
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class JsonPropertyConfig(
|
||||
val isFlat: Boolean = false,
|
||||
|
||||
val mustBePresent: Int = 0,
|
||||
)
|
||||
|
||||
val JsonPropertyConfig.realMustBePresent get() = mustBePresent.toBool()
|
||||
|
||||
/**
|
||||
* Указывает, что для данного класса можно автоматически создать [FactoryAdapter]
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class JsonFactory(
|
||||
/**
|
||||
* @see FactoryAdapter.Builder.storesJson
|
||||
*/
|
||||
val storesJson: Boolean = false,
|
||||
|
||||
/**
|
||||
* @see FactoryAdapter.Builder.logMisses
|
||||
*/
|
||||
val logMisses: Boolean = true,
|
||||
|
||||
/**
|
||||
* @see FactoryAdapter.Builder.inputAsList
|
||||
* @see FactoryAdapter.Builder.inputAsMap
|
||||
*/
|
||||
val asList: Boolean = false
|
||||
)
|
@ -18,14 +18,13 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
||||
import ru.dbotthepony.kstarbound.io.json.util.LazyTypeProvider
|
||||
import ru.dbotthepony.kstarbound.io.json.util.ListAdapter
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.javaType
|
||||
import kotlin.reflect.full.declaredMembers
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
/**
|
||||
* Данный интерфейс имеет один единственный метод: [acceptJson]
|
||||
@ -216,7 +215,6 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
var logMisses: Boolean? = null
|
||||
private val factoryReturnType by lazy { factory.invoke()::class.java }
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType == factoryReturnType) {
|
||||
return build(gson) as TypeAdapter<T>
|
||||
@ -348,7 +346,58 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
companion object : TypeAdapterFactory {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
private fun collectDecl(input: KClass<*>, output: MutableMap<String, KMutableProperty1<*, *>>) {
|
||||
for (decl in input.declaredMembers) {
|
||||
if (decl is KMutableProperty1<*, *>) {
|
||||
decl.isAccessible = true
|
||||
output.putIfAbsent(decl.name, decl)
|
||||
}
|
||||
}
|
||||
|
||||
for (parent in input.supertypes) {
|
||||
if (parent.classifier is KClass<*>) {
|
||||
collectDecl(parent.classifier as KClass<*>, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +384,8 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private var storesJson = false
|
||||
private var logMisses = true
|
||||
private val types = ArrayList<IResolvableProperty<T, *>>()
|
||||
private var stringTransformer: ((String) -> T)? = null
|
||||
|
||||
@ -453,13 +455,6 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Поэтому, конструктор класса ОБЯЗАН принимать [Map]/[ImmutableMap] или [List]/[ImmutableList] первым аргументом,
|
||||
* иначе поиск конструктора завершится неудчаей
|
||||
*/
|
||||
var storesJson = false
|
||||
|
||||
/**
|
||||
* Логировать ли несуществующие свойства у класса когда они попадаются в исходной JSON структуре
|
||||
*/
|
||||
var logMisses = true
|
||||
|
||||
fun storesJson(flag: Boolean = true): Builder<T> {
|
||||
storesJson = flag
|
||||
return this
|
||||
@ -482,7 +477,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически определяет тип поля и необходимый адаптор типа к нему
|
||||
* Автоматически определяет необходимый адаптер типа к свойству при сборке данного адаптера внутри Gson
|
||||
*
|
||||
* Можно указать [transform] для изменения определённого адаптера
|
||||
*/
|
||||
@ -492,13 +487,37 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
var asList = false
|
||||
private var asList = false
|
||||
|
||||
/**
|
||||
* При выставлении данного флага в качестве исходной структуры будет приниматься Json объект:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "thingname": "a",
|
||||
* "count": 4,
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Данный режим используется по умолчанию
|
||||
*
|
||||
* @see inputAsList
|
||||
*/
|
||||
fun inputAsMap(): Builder<T> {
|
||||
asList = false
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* При выставлении данного флага в качестве исходной структуры будет приниматься Json массив:
|
||||
*
|
||||
* ```json
|
||||
* ["a", 4, 100.2, true]
|
||||
* ```
|
||||
*
|
||||
* @see inputAsMap
|
||||
*/
|
||||
fun inputAsList(): Builder<T> {
|
||||
asList = true
|
||||
return this
|
||||
|
Loading…
Reference in New Issue
Block a user