ThingDescription, flat json свойства и делегирование

This commit is contained in:
DBotThePony 2023-01-20 22:08:59 +07:00
parent 4675197a04
commit b077b22180
Signed by: DBot
GPG Key ID: DCC23B5715498507
14 changed files with 281 additions and 61 deletions

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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,
)
}
}

View File

@ -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>()
}
}