ThingDescription, flat json свойства и делегирование
This commit is contained in:
parent
4675197a04
commit
b077b22180
@ -122,7 +122,7 @@ object Starbound {
|
||||
val FUNCTION: Map<String, JsonFunction> = Collections.unmodifiableMap(functions)
|
||||
val ITEM: Map<String, IItemDefinition> = Collections.unmodifiableMap(items)
|
||||
|
||||
val STRING_INTERNER: Interner<String> = Interners.newStrongInterner()
|
||||
val STRING_INTERNER: Interner<String> = Interners.newWeakInterner()
|
||||
|
||||
val STRING_ADAPTER: TypeAdapter<String> = object : TypeAdapter<String>() {
|
||||
override fun write(out: JsonWriter, value: String) {
|
||||
@ -171,6 +171,7 @@ object Starbound {
|
||||
|
||||
.registerTypeAdapter(LeveledStatusEffect.ADAPTER)
|
||||
.registerTypeAdapter(MaterialReference.Companion)
|
||||
.registerTypeAdapter(ThingDescription.ADAPTER)
|
||||
|
||||
.registerTypeAdapter(ItemPrototype.ADAPTER)
|
||||
.registerTypeAdapter(CurrencyItemPrototype.ADAPTER)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import ru.dbotthepony.kstarbound.io.json.FactoryAdapter
|
||||
|
||||
interface IThingWithDescription {
|
||||
/**
|
||||
* Краткое описание штуки. Несмотря на то, что название свойства подразумевает "описание",
|
||||
@ -21,4 +23,35 @@ interface IThingWithDescription {
|
||||
* * The Poptop hums beautifully to confuse its prey.
|
||||
*/
|
||||
val description: String
|
||||
|
||||
/**
|
||||
* Полное описание штуки для определённых рас
|
||||
*
|
||||
* Пример для Microwave Oven:
|
||||
* Apex: A type of oven.
|
||||
* Avian: A bizarre cooking device.
|
||||
* Floran: Floran likess raw meat, sssometimes cooked meat is good too.
|
||||
* Glitch: Irked. It is encrusted with spattered food, who left it in this state?
|
||||
* Human: A microwave. Gotta get me some jacket potatoes.
|
||||
* Hylotl: A strange, spinning oven.
|
||||
* Novakid: This lil' rotatin' oven cooks food at speed!
|
||||
*/
|
||||
val racialDescription: Map<String, String> get() = mapOf()
|
||||
|
||||
/**
|
||||
* Кратное описание штуки для определённых рас
|
||||
*/
|
||||
val racialShortDescription: Map<String, String> get() = mapOf()
|
||||
}
|
||||
|
||||
data class ThingDescription(
|
||||
override val shortdescription: String,
|
||||
override val description: String
|
||||
) : IThingWithDescription {
|
||||
companion object {
|
||||
val ADAPTER = FactoryAdapter.Builder(ThingDescription::class,
|
||||
ThingDescription::shortdescription,
|
||||
ThingDescription::description)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
|
||||
data class ArmorItemDefinition(
|
||||
override val shortdescription: String,
|
||||
override val description: String,
|
||||
override val itemName: String,
|
||||
override val price: Long,
|
||||
override val rarity: ItemRarity,
|
||||
@ -29,5 +30,7 @@ data class ArmorItemDefinition(
|
||||
|
||||
override val armorType: ArmorPieceType,
|
||||
|
||||
val descriptionData: ThingDescription,
|
||||
|
||||
val json: Map<String, Any>,
|
||||
) : IArmorItemDefinition
|
||||
) : IArmorItemDefinition, IThingWithDescription by descriptionData
|
||||
|
@ -26,8 +26,7 @@ class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition {
|
||||
|
||||
override fun assemble(): IItemDefinition {
|
||||
return ArmorItemDefinition(
|
||||
shortdescription = shortdescription,
|
||||
description = description,
|
||||
descriptionData = descriptionData,
|
||||
itemName = itemName,
|
||||
price = price,
|
||||
rarity = rarity,
|
||||
|
@ -1,8 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
|
||||
data class CurrencyItemDefinition(
|
||||
override val shortdescription: String,
|
||||
override val description: String,
|
||||
override val itemName: String,
|
||||
override val price: Long,
|
||||
override val rarity: ItemRarity,
|
||||
@ -27,5 +28,7 @@ data class CurrencyItemDefinition(
|
||||
override val currency: String,
|
||||
override val value: Long,
|
||||
|
||||
val descriptionData: ThingDescription,
|
||||
|
||||
val json: Map<String, Any>,
|
||||
) : ICurrencyItemDefinition
|
||||
) : ICurrencyItemDefinition, IThingWithDescription by descriptionData
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
@ -19,8 +20,7 @@ class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition {
|
||||
|
||||
override fun assemble(): IItemDefinition {
|
||||
return CurrencyItemDefinition(
|
||||
shortdescription = shortdescription,
|
||||
description = description,
|
||||
descriptionData = descriptionData,
|
||||
itemName = itemName,
|
||||
price = price,
|
||||
rarity = rarity,
|
||||
|
@ -1,8 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
|
||||
data class ItemDefinition(
|
||||
override val shortdescription: String,
|
||||
override val description: String,
|
||||
override val itemName: String,
|
||||
override val price: Long,
|
||||
override val rarity: ItemRarity,
|
||||
@ -19,5 +20,7 @@ data class ItemDefinition(
|
||||
override val radioMessagesOnPickup: List<String>,
|
||||
override val fuelAmount: Long?,
|
||||
|
||||
val descriptionData: ThingDescription,
|
||||
|
||||
val json: Map<String, Any>
|
||||
) : IItemDefinition
|
||||
) : IItemDefinition, IThingWithDescription by descriptionData
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.enrollMap
|
||||
import ru.dbotthepony.kstarbound.io.json.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.INativeJsonHolder
|
||||
@ -24,6 +25,8 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
final override var radioMessagesOnPickup: List<String> = listOf()
|
||||
final override var fuelAmount: Long? = null
|
||||
|
||||
var descriptionData: ThingDescription by NotNullVar()
|
||||
|
||||
var json: Map<String, Any> = mapOf()
|
||||
|
||||
final override fun acceptJson(json: MutableMap<String, Any>) {
|
||||
@ -32,8 +35,7 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
|
||||
open fun assemble(): IItemDefinition {
|
||||
return ItemDefinition(
|
||||
shortdescription = shortdescription,
|
||||
description = description,
|
||||
descriptionData = descriptionData,
|
||||
itemName = itemName,
|
||||
price = price,
|
||||
rarity = rarity,
|
||||
@ -78,6 +80,7 @@ open class ItemPrototype : IItemDefinition, INativeJsonHolder {
|
||||
auto(ItemPrototype::twoHanded)
|
||||
autoList(ItemPrototype::radioMessagesOnPickup)
|
||||
auto(ItemPrototype::fuelAmount)
|
||||
flat(ItemPrototype::descriptionData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.MaterialReference
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
|
||||
data class LiquidItemDefinition(
|
||||
override val shortdescription: String,
|
||||
override val description: String,
|
||||
override val itemName: String,
|
||||
override val price: Long,
|
||||
override val rarity: ItemRarity,
|
||||
@ -23,5 +23,7 @@ data class LiquidItemDefinition(
|
||||
|
||||
override val liquid: MaterialReference,
|
||||
|
||||
val descriptionData: ThingDescription,
|
||||
|
||||
val json: Map<String, Any>
|
||||
) : ILiquidItem
|
||||
) : ILiquidItem, IThingWithDescription by descriptionData
|
||||
|
@ -20,8 +20,7 @@ class LiquidItemPrototype : ItemPrototype() {
|
||||
|
||||
override fun assemble(): IItemDefinition {
|
||||
return LiquidItemDefinition(
|
||||
shortdescription = shortdescription,
|
||||
description = description,
|
||||
descriptionData = descriptionData,
|
||||
itemName = itemName,
|
||||
price = price,
|
||||
rarity = rarity,
|
||||
|
@ -1,10 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
import ru.dbotthepony.kstarbound.defs.MaterialReference
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
|
||||
data class MaterialItemDefinition(
|
||||
override val shortdescription: String,
|
||||
override val description: String,
|
||||
override val itemName: String,
|
||||
override val price: Long,
|
||||
override val rarity: ItemRarity,
|
||||
@ -23,5 +23,7 @@ data class MaterialItemDefinition(
|
||||
|
||||
override val material: MaterialReference,
|
||||
|
||||
val descriptionData: ThingDescription,
|
||||
|
||||
val json: Map<String, Any>
|
||||
) : IMaterialItem
|
||||
) : IMaterialItem, IThingWithDescription by descriptionData
|
||||
|
@ -20,8 +20,7 @@ class MaterialItemPrototype : ItemPrototype() {
|
||||
|
||||
override fun assemble(): IItemDefinition {
|
||||
return MaterialItemDefinition(
|
||||
shortdescription = shortdescription,
|
||||
description = description,
|
||||
descriptionData = descriptionData,
|
||||
itemName = itemName,
|
||||
price = price,
|
||||
rarity = rarity,
|
||||
|
@ -16,6 +16,7 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.flattenJsonElement
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
@ -68,6 +69,11 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
*/
|
||||
val properties: ImmutableMap<String, WrappedProperty<T, *>>,
|
||||
|
||||
/**
|
||||
* Свойства объекта [T], которые можно выставлять
|
||||
*/
|
||||
val flatProperties: ImmutableMap<String, WrappedProperty<T, *>>,
|
||||
|
||||
/**
|
||||
* Ключи, которые необходимо игнорировать при чтении JSON
|
||||
*/
|
||||
@ -77,6 +83,11 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
* @see Builder.extraPropertiesAreFatal
|
||||
*/
|
||||
val extraPropertiesAreFatal: Boolean,
|
||||
|
||||
/**
|
||||
* @see Builder.logMisses
|
||||
*/
|
||||
val logMisses: Boolean,
|
||||
) : TypeAdapter<T>() {
|
||||
private val loggedMisses = ObjectOpenHashSet<String>()
|
||||
|
||||
@ -92,15 +103,16 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
missing.addAll(properties.values)
|
||||
|
||||
val instance = factory.invoke()
|
||||
var json: JsonObject by Delegates.notNull()
|
||||
|
||||
if (instance is IJsonHolder) {
|
||||
val obj = TypeAdapters.JSON_ELEMENT.read(reader)
|
||||
reader = JsonTreeReader(obj)
|
||||
if (instance is IJsonHolder || flatProperties.isNotEmpty()) {
|
||||
json = TypeAdapters.JSON_ELEMENT.read(reader) as JsonObject
|
||||
reader = JsonTreeReader(json)
|
||||
|
||||
if (instance is INativeJsonHolder) {
|
||||
instance.acceptJson(flattenJsonElement(obj.asJsonObject, Starbound.STRING_INTERNER::intern))
|
||||
} else {
|
||||
instance.acceptJson(obj.asJsonObject)
|
||||
instance.acceptJson(flattenJsonElement(json, Starbound.STRING_INTERNER::intern))
|
||||
} else if (instance is IJsonHolder) {
|
||||
instance.acceptJson(json)
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +154,7 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
throw JsonSyntaxException("$name is not a valid property of ${instance::class.qualifiedName}")
|
||||
}
|
||||
|
||||
if (!loggedMisses.contains(name)) {
|
||||
if (logMisses && !loggedMisses.contains(name)) {
|
||||
LOGGER.warn("${instance::class.qualifiedName} has no property for storing $name")
|
||||
loggedMisses.add(name)
|
||||
}
|
||||
@ -153,9 +165,25 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
|
||||
reader.endObject()
|
||||
|
||||
for (property in flatProperties.values) {
|
||||
try {
|
||||
val read = property.adapter.read(JsonTreeReader(json))
|
||||
|
||||
if (!property.returnType.isMarkedNullable && read == null) {
|
||||
throw NullPointerException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (flat property adapter returned NULL)")
|
||||
} else if (read == null) {
|
||||
property.set(instance, null)
|
||||
} else {
|
||||
property.set(instance, read)
|
||||
}
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading flat property ${property.name} of ${instance::class.qualifiedName} near ${reader.path}", err)
|
||||
}
|
||||
}
|
||||
|
||||
for (property in missing) {
|
||||
if (property.mustBePresent == true) {
|
||||
throw JsonSyntaxException("${instance::class.qualifiedName} demands for ${property.name} to be present, however, it is missing")
|
||||
throw JsonSyntaxException("${instance::class.qualifiedName} demands for ${property.name} to be present, however, it is missing from JSON structure")
|
||||
} else if (property.mustBePresent == null) {
|
||||
if (property.returnType.isMarkedNullable) {
|
||||
continue
|
||||
@ -214,8 +242,10 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
|
||||
class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) {
|
||||
private val properties = ArrayList<WrappedProperty<T, *>>()
|
||||
private val flatProperties = ArrayList<WrappedProperty<T, *>>()
|
||||
private val ignoreKeys = ObjectArraySet<String>()
|
||||
var extraPropertiesAreFatal = false
|
||||
var logMisses = true
|
||||
|
||||
/**
|
||||
* Являются ли "лишние" ключи в JSON структуре ошибкой.
|
||||
@ -224,24 +254,45 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
* то [extraPropertiesAreFatal] можно скомбинировать с [ignoreKey].
|
||||
*/
|
||||
fun extraPropertiesAreFatal(flag: Boolean = true): Builder<T> {
|
||||
check(flatProperties.isEmpty() || !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 {
|
||||
for (field in fields)
|
||||
auto(field)
|
||||
}
|
||||
|
||||
fun <V> add(property: KMutableProperty1<T, V>, adapter: TypeAdapter<V>, configurator: PropertyConfigurator<T, V>.() -> Unit = {}): Builder<T> {
|
||||
private fun <V> _add(property: KMutableProperty1<T, V>, adapter: TypeAdapter<V>, configurator: PropertyConfigurator<T, V>.() -> Unit): PropertyConfigurator<T, *> {
|
||||
if (properties.any { it.property == property }) {
|
||||
throw IllegalArgumentException("Property $property is defined twice")
|
||||
}
|
||||
|
||||
if (flatProperties.any { it.property == property }) {
|
||||
throw IllegalArgumentException("Property $property is defined twice")
|
||||
}
|
||||
|
||||
ignoreKeys.remove(property.name)
|
||||
|
||||
val config = PropertyConfigurator(property, adapter)
|
||||
configurator.invoke(config)
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет указанное свойство в будущий адаптер с указанным [adapter]
|
||||
*/
|
||||
fun <V> add(property: KMutableProperty1<T, V>, adapter: TypeAdapter<V>, configurator: PropertyConfigurator<T, V>.() -> Unit = {}): Builder<T> {
|
||||
val config = _add(property, adapter, configurator)
|
||||
|
||||
properties.add(WrappedProperty(
|
||||
property,
|
||||
@ -252,6 +303,38 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет указанное свойство в будущий адаптер, как плоский объект внутри данного на том же уровне, что и сам объект, используя адаптер [adapter]
|
||||
*
|
||||
* Пример:
|
||||
* ```json
|
||||
* {
|
||||
* "prop_belong_to_a_1": ...,
|
||||
* "prop_belong_to_a_2": ...,
|
||||
* "prop_belong_to_b_1": ...,
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* В данном случае, можно указать `b` как плоский класс внутри `a`.
|
||||
*
|
||||
* Данный подход позволяет избавиться от постоянного наследования и реализации одного и того же интерфейса во множестве других классов.
|
||||
*
|
||||
* Флаг [extraPropertiesAreFatal] не поддерживается с данными свойствами
|
||||
*/
|
||||
fun <V> flat(property: KMutableProperty1<T, V>, adapter: TypeAdapter<V>, configurator: PropertyConfigurator<T, V>.() -> Unit = {}): Builder<T> {
|
||||
val config = _add(property, adapter, configurator)
|
||||
|
||||
check(!extraPropertiesAreFatal) { "Can't have both flattened properties and extraPropertiesAreFatal" }
|
||||
|
||||
flatProperties.add(WrappedProperty(
|
||||
property,
|
||||
adapter,
|
||||
mustBePresent = config.mustBePresent
|
||||
))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически определяет тип свойства и необходимый [TypeAdapter]
|
||||
*/
|
||||
@ -271,6 +354,25 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
return add(property, LazyTypeProvider(classifier.java) as TypeAdapter<V>, configurator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически определяет тип свойства и необходимый [TypeAdapter], инкапсулированный в данный типе на одном уровне
|
||||
*/
|
||||
fun <V> flat(property: KMutableProperty1<T, V>, configurator: PropertyConfigurator<T, V>.() -> Unit = {}): Builder<T> {
|
||||
val returnType = property.returnType
|
||||
val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${property.name}!")
|
||||
|
||||
if (classifier.isSubclassOf(List::class)) {
|
||||
throw IllegalArgumentException("${property.name} is a List, please use autoList() or directly specify type adapter method instead")
|
||||
}
|
||||
|
||||
if (classifier.isSubclassOf(Map::class)) {
|
||||
throw IllegalArgumentException("${property.name} is a Map, please use autoMap() or directly specify type adapter method instead")
|
||||
}
|
||||
|
||||
@Suppress("unchecked_cast") // classifier.java не имеет обозначенного типа
|
||||
return flat(property, LazyTypeProvider(classifier.java) as TypeAdapter<V>, configurator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически создаёт [ListAdapter] для заданного свойства
|
||||
*/
|
||||
@ -300,11 +402,18 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
for (property in properties)
|
||||
map.put(property.property.name, property)
|
||||
|
||||
val map2 = ImmutableMap.Builder<String, WrappedProperty<T, *>>()
|
||||
|
||||
for (property in flatProperties)
|
||||
map2.put(property.property.name, property)
|
||||
|
||||
return BuilderAdapter(
|
||||
factory = factory,
|
||||
properties = map.build(),
|
||||
flatProperties = map2.build(),
|
||||
ignoreKeys = ImmutableSet.copyOf(ignoreKeys),
|
||||
extraPropertiesAreFatal = extraPropertiesAreFatal,
|
||||
logMisses = logMisses,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,18 +23,11 @@ import ru.dbotthepony.kstarbound.getValue
|
||||
import ru.dbotthepony.kstarbound.setValue
|
||||
import java.lang.reflect.Constructor
|
||||
import kotlin.jvm.internal.DefaultConstructorMarker
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.*
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.full.isSupertypeOf
|
||||
|
||||
private data class PackedProperty<Clazz : Any, T>(
|
||||
val property: KProperty1<Clazz, T>,
|
||||
val adapter: TypeAdapter<T>,
|
||||
val transformer: (T) -> T = { it }
|
||||
) {
|
||||
val returnType = property.returnType
|
||||
}
|
||||
|
||||
/**
|
||||
* [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе.
|
||||
*/
|
||||
@ -44,11 +37,18 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
val asJsonArray: Boolean,
|
||||
val storesJson: Boolean
|
||||
) : TypeAdapter<T>() {
|
||||
private data class PackedProperty<Clazz : Any, T>(
|
||||
val property: KProperty1<Clazz, T>,
|
||||
val adapter: TypeAdapter<T>,
|
||||
val transformer: (T) -> T = { it },
|
||||
val isFlat: Boolean
|
||||
) {
|
||||
val returnType = property.returnType
|
||||
}
|
||||
|
||||
private val name2index = Object2IntArrayMap<String>()
|
||||
private val loggedMisses = ObjectArraySet<String>()
|
||||
|
||||
var currentSymbolicName by ThreadLocal<String>()
|
||||
|
||||
init {
|
||||
name2index.defaultReturnValue(-1)
|
||||
|
||||
@ -218,20 +218,24 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
readValues[fieldId] = (tuple.transformer as (Any?) -> Any?)(adapter.read(reader))
|
||||
presentValues[fieldId] = true
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err)
|
||||
throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err)
|
||||
}
|
||||
|
||||
fieldId++
|
||||
}
|
||||
// иначе - читаем как json object
|
||||
} else {
|
||||
if (storesJson) {
|
||||
var json: JsonObject by Delegates.notNull()
|
||||
val hasFlatValues = types.any { it.isFlat }
|
||||
|
||||
if (storesJson || hasFlatValues) {
|
||||
val readMap = TypeAdapters.JSON_ELEMENT.read(reader)
|
||||
|
||||
if (readMap !is JsonObject) {
|
||||
throw JsonParseException("Expected JSON element to be a Map, ${readMap::class.qualifiedName} given")
|
||||
}
|
||||
|
||||
json = readMap
|
||||
reader = JsonTreeReader(readMap)
|
||||
readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map<String, Any>, Starbound.STRING_INTERNER::intern)
|
||||
}
|
||||
@ -243,7 +247,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
val fieldId = name2index.getInt(name)
|
||||
|
||||
if (fieldId == -1) {
|
||||
if (!storesJson && loggedMisses.add(name)) {
|
||||
if (!storesJson && !hasFlatValues && loggedMisses.add(name)) {
|
||||
if (currentSymbolicName == null) {
|
||||
LOGGER.warn("${bound.qualifiedName} has no property for storing $name ")
|
||||
} else {
|
||||
@ -254,17 +258,34 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
reader.skipValue()
|
||||
} else {
|
||||
val tuple = types[fieldId]
|
||||
|
||||
if (tuple.isFlat) {
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
|
||||
val (field, adapter) = tuple
|
||||
|
||||
try {
|
||||
readValues[fieldId] = (tuple.transformer as (Any?) -> Any?)(adapter.read(reader))
|
||||
presentValues[fieldId] = true
|
||||
} catch(err: Throwable) {
|
||||
if (currentSymbolicName == null) {
|
||||
throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err)
|
||||
} else {
|
||||
throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName} (reading: $currentSymbolicName)", err)
|
||||
throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((i, property) in types.withIndex()) {
|
||||
if (property.isFlat) {
|
||||
try {
|
||||
val read = property.adapter.read(JsonTreeReader(json))
|
||||
|
||||
if (read != null) {
|
||||
presentValues[i] = true
|
||||
readValues[i] = read
|
||||
}
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading flat field ${property.property.name} for ${bound.qualifiedName}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -381,10 +402,18 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле с определённым адаптером
|
||||
* Добавляет свойство с определённым [adapter]
|
||||
*/
|
||||
fun <V> add(field: KProperty1<T, V>, adapter: TypeAdapter<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, adapter))
|
||||
types.add(PackedProperty(field, adapter, isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет свойство с определённым [adapter], которое находится на том же уровне что и данный объект внутри JSON структуры
|
||||
*/
|
||||
fun <V> flat(field: KProperty1<T, V>, adapter: TypeAdapter<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, adapter, isFlat = true))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -399,6 +428,17 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поля без generic типов и без преобразователей
|
||||
*/
|
||||
@Suppress("unchecked_cast")
|
||||
fun autoFlat(vararg fields: KProperty1<T, *>): Builder<T> {
|
||||
for (field in fields)
|
||||
autoFlat(field)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически определяет тип поля и необходимый адаптор типа к нему
|
||||
*/
|
||||
@ -415,7 +455,27 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
throw IllegalArgumentException("${field.name} is a Map, please use autoMap() method instead")
|
||||
}
|
||||
|
||||
types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter<Any?>, transformer = transformer as (Any?) -> Any?))
|
||||
types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter<Any?>, transformer = transformer as (Any?) -> Any?, isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматически определяет тип поля и необходимый адаптор типа к нему
|
||||
*/
|
||||
@Suppress("unchecked_cast")
|
||||
fun <In> autoFlat(field: KProperty1<T, In>, transformer: (In) -> In = { it }): Builder<T> {
|
||||
val returnType = field.returnType
|
||||
val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!")
|
||||
|
||||
if (classifier.isSubclassOf(List::class)) {
|
||||
throw IllegalArgumentException("${field.name} is a List")
|
||||
}
|
||||
|
||||
if (classifier.isSubclassOf(Map::class)) {
|
||||
throw IllegalArgumentException("${field.name} is a Map")
|
||||
}
|
||||
|
||||
types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter<Any?>, transformer = transformer as (Any?) -> Any?, isFlat = true))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -425,7 +485,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Список неизменяем (создаётся объект [ImmutableList])
|
||||
*/
|
||||
fun <V : Any> list(field: KProperty1<T, List<V>?>, type: Class<V>, transformer: (V) -> V = { it }): Builder<T> {
|
||||
types.add(PackedProperty(field, ListAdapter(type, transformer).nullSafe()))
|
||||
types.add(PackedProperty(field, ListAdapter(type, transformer).nullSafe(), isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -444,7 +504,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <K, V> mapAsArray(field: KProperty1<T, Map<K, V>>, keyType: Class<K>, valueType: Class<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, MapAdapter(keyType, valueType)))
|
||||
types.add(PackedProperty(field, MapAdapter(keyType, valueType), isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -468,7 +528,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <K : Any, V : Any> mapAsArray(field: KProperty1<T, Map<K, V>?>, keyType: KClass<K>, valueType: KClass<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, MapAdapter(keyType.java, valueType.java).nullSafe()))
|
||||
types.add(PackedProperty(field, MapAdapter(keyType.java, valueType.java).nullSafe(), isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -478,7 +538,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <V> mapAsObject(field: KProperty1<T, Map<String, V>?>, valueType: Class<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, String2ObjectAdapter(valueType).nullSafe()))
|
||||
types.add(PackedProperty(field, String2ObjectAdapter(valueType).nullSafe(), isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -488,7 +548,7 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <V : Any> mapAsObject(field: KProperty1<T, Map<String, V>?>, valueType: KClass<V>): Builder<T> {
|
||||
types.add(PackedProperty(field, String2ObjectAdapter(valueType.java).nullSafe()))
|
||||
types.add(PackedProperty(field, String2ObjectAdapter(valueType.java).nullSafe(), isFlat = false))
|
||||
return this
|
||||
}
|
||||
|
||||
@ -505,16 +565,20 @@ class FactoryAdapter<T : Any> private constructor(
|
||||
}
|
||||
|
||||
fun build(): FactoryAdapter<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),
|
||||
asJsonArray = asList,
|
||||
storesJson = storesJson
|
||||
storesJson = storesJson,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
var currentSymbolicName by ThreadLocal<String>()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user