Ещё больше json аннотаций, теперь для FactoryAdapter
This commit is contained in:
parent
f7c8455b87
commit
6bcf504908
@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype
|
|||||||
import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.IFossilItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.IFossilItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.IItemDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.item.ItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemPrototype
|
import ru.dbotthepony.kstarbound.defs.item.ItemPrototype
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
|
||||||
import ru.dbotthepony.kstarbound.defs.item.ItemTooltipKind
|
import ru.dbotthepony.kstarbound.defs.item.ItemTooltipKind
|
||||||
@ -47,6 +48,8 @@ import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
|||||||
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
@ -151,6 +154,9 @@ object Starbound {
|
|||||||
// чтоб строки всегда intern'ились
|
// чтоб строки всегда intern'ились
|
||||||
.registerTypeAdapter(NULLABLE_STRING_ADAPTER)
|
.registerTypeAdapter(NULLABLE_STRING_ADAPTER)
|
||||||
|
|
||||||
|
// Обработчик @JsonImplementation
|
||||||
|
.registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
||||||
|
|
||||||
// ImmutableList, ImmutableSet, ImmutableMap
|
// ImmutableList, ImmutableSet, ImmutableMap
|
||||||
.registerTypeAdapterFactory(ImmutableCollectionAdapterFactory)
|
.registerTypeAdapterFactory(ImmutableCollectionAdapterFactory)
|
||||||
|
|
||||||
@ -160,9 +166,12 @@ object Starbound {
|
|||||||
// все enum'ы без особых настроек
|
// все enum'ы без особых настроек
|
||||||
.registerTypeAdapterFactory(EnumAdapter.Companion)
|
.registerTypeAdapterFactory(EnumAdapter.Companion)
|
||||||
|
|
||||||
// автоматическое создание BuilderAdapter
|
// автоматическое создание BuilderAdapter по @аннотациям
|
||||||
.registerTypeAdapterFactory(BuilderAdapter.Companion)
|
.registerTypeAdapterFactory(BuilderAdapter.Companion)
|
||||||
|
|
||||||
|
// автоматическое создание FactoryAdapter по @аннотациям
|
||||||
|
.registerTypeAdapterFactory(FactoryAdapter.Companion)
|
||||||
|
|
||||||
.also(::addStarboundJsonAdapters)
|
.also(::addStarboundJsonAdapters)
|
||||||
|
|
||||||
.create()
|
.create()
|
||||||
|
@ -5,9 +5,11 @@ import com.google.gson.TypeAdapter
|
|||||||
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 ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
|
||||||
import ru.dbotthepony.kstarbound.sbIntern
|
import ru.dbotthepony.kstarbound.sbIntern
|
||||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||||
|
|
||||||
|
@JsonImplementation(ThingDescription::class)
|
||||||
interface IThingWithDescription {
|
interface IThingWithDescription {
|
||||||
/**
|
/**
|
||||||
* Краткое описание штуки. Несмотря на то, что название свойства подразумевает "описание",
|
* Краткое описание штуки. Несмотря на то, что название свойства подразумевает "описание",
|
||||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kstarbound.Starbound
|
|||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
|
||||||
import ru.dbotthepony.kstarbound.io.json.ifString
|
import ru.dbotthepony.kstarbound.io.json.ifString
|
||||||
|
|
||||||
interface IItemDefinition : IThingWithDescription {
|
interface IItemDefinition : IThingWithDescription {
|
||||||
@ -36,6 +37,7 @@ interface IItemDefinition : IThingWithDescription {
|
|||||||
*/
|
*/
|
||||||
val inventoryIcon: List<IInventoryIcon>?
|
val inventoryIcon: List<IInventoryIcon>?
|
||||||
|
|
||||||
|
@JsonImplementation(InventoryIcon::class)
|
||||||
interface IInventoryIcon {
|
interface IInventoryIcon {
|
||||||
val image: SpriteReference
|
val image: SpriteReference
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package ru.dbotthepony.kstarbound.defs.item
|
|||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
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.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonPropertyConfig
|
||||||
|
|
||||||
|
@JsonFactory(storesJson = true)
|
||||||
data class ItemDefinition(
|
data class ItemDefinition(
|
||||||
override val itemName: String,
|
override val itemName: String,
|
||||||
override val price: Long,
|
override val price: Long = 4L,
|
||||||
override val rarity: ItemRarity,
|
override val rarity: ItemRarity,
|
||||||
override val category: String?,
|
override val category: String?,
|
||||||
override val inventoryIcon: ImmutableList<out IItemDefinition.IInventoryIcon>?,
|
override val inventoryIcon: ImmutableList<out IItemDefinition.IInventoryIcon>?,
|
||||||
@ -21,6 +24,7 @@ data class ItemDefinition(
|
|||||||
override val radioMessagesOnPickup: ImmutableList<String>,
|
override val radioMessagesOnPickup: ImmutableList<String>,
|
||||||
override val fuelAmount: Long?,
|
override val fuelAmount: Long?,
|
||||||
|
|
||||||
|
@JsonPropertyConfig(isFlat = true)
|
||||||
val descriptionData: ThingDescription,
|
val descriptionData: ThingDescription,
|
||||||
|
|
||||||
val json: Map<String, Any>
|
val json: Map<String, Any>
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.io.json.builder
|
package ru.dbotthepony.kstarbound.io.json.builder
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
private fun Int.toBool() = if (this == 0) null else this > 0
|
private fun Int.toBool() = if (this == 0) null else this > 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +66,14 @@ val JsonPropertyConfig.realMustBePresent get() = mustBePresent.toBool()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Указывает, что для данного класса можно автоматически создать [FactoryAdapter]
|
* Указывает, что для данного класса можно автоматически создать [FactoryAdapter]
|
||||||
|
*
|
||||||
|
* В подавляющем большинстве случаев это работает исключительно с data классами
|
||||||
|
*
|
||||||
|
* С технической точки зрения, у класса определяются все свойства и происходит поиск *главного* конструктора, а
|
||||||
|
* затем аргументы конструктора отражаются на свойства класса
|
||||||
|
*
|
||||||
|
* @see JsonIgnoreProperty
|
||||||
|
* @see JsonPropertyConfig
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@ -80,3 +94,34 @@ annotation class JsonFactory(
|
|||||||
*/
|
*/
|
||||||
val asList: Boolean = false
|
val asList: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Позволяет указать, какую реализацию использовать при попытке разобрать JSON структуру
|
||||||
|
* с аннотированным типом
|
||||||
|
*
|
||||||
|
* Пример:
|
||||||
|
* ```kotlin
|
||||||
|
* @JsonFactory
|
||||||
|
* data class Item(val thingDef: IThing, val price: Long)
|
||||||
|
*
|
||||||
|
* @JsonImplementation(Thing::class)
|
||||||
|
* interface IThing { val prop: String }
|
||||||
|
*
|
||||||
|
* data class Thing(override val prop: String) : IThing
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class JsonImplementation(val implementingClass: KClass<*>)
|
||||||
|
|
||||||
|
object JsonImplementationTypeFactory : TypeAdapterFactory {
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
val delegate = type.rawType.getAnnotation(JsonImplementation::class.java)
|
||||||
|
|
||||||
|
if (delegate != null) {
|
||||||
|
return gson.getAdapter(delegate.implementingClass.java) as TypeAdapter<T>?
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,8 +33,12 @@ import java.lang.reflect.Constructor
|
|||||||
import kotlin.jvm.internal.DefaultConstructorMarker
|
import kotlin.jvm.internal.DefaultConstructorMarker
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
import kotlin.reflect.*
|
import kotlin.reflect.*
|
||||||
|
import kotlin.reflect.full.declaredMembers
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
import kotlin.reflect.full.isSubtypeOf
|
||||||
import kotlin.reflect.full.isSupertypeOf
|
import kotlin.reflect.full.isSupertypeOf
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе.
|
* [TypeAdapter] для классов, которые имеют все свои свойства в главном конструкторе.
|
||||||
@ -452,8 +456,8 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
*
|
*
|
||||||
* На самом деле, JSON "заворачивается" в [ImmutableMap], или [ImmutableList] если указано [asList]/[inputAsList]
|
* На самом деле, JSON "заворачивается" в [ImmutableMap], или [ImmutableList] если указано [asList]/[inputAsList]
|
||||||
*
|
*
|
||||||
* Поэтому, конструктор класса ОБЯЗАН принимать [Map]/[ImmutableMap] или [List]/[ImmutableList] первым аргументом,
|
* Поэтому, конструктор класса ОБЯЗАН принимать [Map]/[ImmutableMap] или [List]/[ImmutableList] последним аргументом,
|
||||||
* иначе поиск конструктора завершится неудчаей
|
* иначе поиск конструктора завершится неудачей
|
||||||
*/
|
*/
|
||||||
fun storesJson(flag: Boolean = true): Builder<T> {
|
fun storesJson(flag: Boolean = true): Builder<T> {
|
||||||
storesJson = flag
|
storesJson = flag
|
||||||
@ -524,9 +528,53 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object : TypeAdapterFactory {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
var currentSymbolicName by ThreadLocal<String>()
|
var currentSymbolicName by ThreadLocal<String>()
|
||||||
|
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
val raw = type.rawType
|
||||||
|
|
||||||
|
if (raw.isAnnotationPresent(JsonFactory::class.java)) {
|
||||||
|
val first = raw.getAnnotationsByType(JsonFactory::class.java)
|
||||||
|
require(first.size == 1) { "Multiple JsonFactory defined: ${first.joinToString(", ")}" }
|
||||||
|
|
||||||
|
val bconfig = first[0] as JsonFactory
|
||||||
|
val kclass = raw.kotlin as KClass<T>
|
||||||
|
val builder = Builder(kclass)
|
||||||
|
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()
|
||||||
|
|
||||||
|
if (bconfig.asList) {
|
||||||
|
builder.inputAsList()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.storesJson(bconfig.storesJson)
|
||||||
|
builder.logMisses(bconfig.logMisses)
|
||||||
|
|
||||||
|
if (properties.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("${kclass.qualifiedName} has no valid members")
|
||||||
|
}
|
||||||
|
|
||||||
|
val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}")
|
||||||
|
|
||||||
|
if (!bconfig.storesJson) {
|
||||||
|
for (argument in foundConstructor.parameters) {
|
||||||
|
builder.auto(properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val params = foundConstructor.parameters
|
||||||
|
|
||||||
|
for (i in 0 until params.size - 1) {
|
||||||
|
val argument = params[i]
|
||||||
|
builder.auto(properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build(gson)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user