KConcreteTypeAdapter test
This commit is contained in:
parent
0866edcb74
commit
e2b17f5761
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.Version
|
||||
@ -8,6 +9,7 @@ import ru.dbotthepony.kbox2d.api.*
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.defs.MaterialModifier
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
|
||||
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
|
||||
@ -39,6 +41,34 @@ fun main() {
|
||||
//return
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val input = "{\n" +
|
||||
" \"modId\" : 26,\n" +
|
||||
" \"modName\" : \"aegisalt\",\n" +
|
||||
" \"itemDrop\" : \"aegisaltore\",\n" +
|
||||
" \"description\" : \"Aegisalt.\",\n" +
|
||||
" \"health\" : 5,\n" +
|
||||
" \"harvestLevel\" : 5,\n" +
|
||||
" \"breaksWithTile\" : true,\n" +
|
||||
"\n" +
|
||||
" \"miningSounds\" : [ \"/sfx/tools/pickaxe_ore.ogg\", \"/sfx/tools/pickaxe_ore2.ogg\" ],\n" +
|
||||
" \"miningParticle\" : \"orespark\",\n" +
|
||||
"\n" +
|
||||
" \"renderTemplate\" : \"/tiles/classicmaterialtemplate.config\",\n" +
|
||||
" \"renderParameters\" : {\n" +
|
||||
" \"texture\" : \"aegisalt.png\",\n" +
|
||||
" \"variants\" : 8,\n" +
|
||||
" \"multiColored\" : false,\n" +
|
||||
" \"zLevel\" : 0\n" +
|
||||
" }\n" +
|
||||
"}\n"
|
||||
|
||||
val json = Starbound.gson.fromJson(input, MaterialModifier::class.java)
|
||||
|
||||
println(json)
|
||||
return
|
||||
}
|
||||
|
||||
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
|
||||
//val db = BTreeDB(File("world.world"))
|
||||
|
||||
|
@ -71,6 +71,7 @@ object Starbound : IVFS {
|
||||
.also(DungeonWorldDef::registerGson)
|
||||
.also(ParallaxPrototype::registerGson)
|
||||
.also(JsonFunction::registerGson)
|
||||
.also(MaterialModifier::registerGson)
|
||||
|
||||
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
||||
|
||||
@ -226,21 +227,19 @@ object Starbound : IVFS {
|
||||
|
||||
private fun loadTileMaterials(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFiles("tiles/materials")) {
|
||||
if (listedFile.endsWith(".material")) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
for (listedFile in fs.listAllFilesWithExtension("material")) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials")
|
||||
val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials")
|
||||
|
||||
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
|
||||
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
||||
tilesByMaterialID[tileDef.materialId] = tileDef
|
||||
tiles[tileDef.materialName] = tileDef
|
||||
} catch (err: Throwable) {
|
||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||
LOGGER.error("Loading tile file $listedFile", err)
|
||||
}
|
||||
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
|
||||
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
||||
tilesByMaterialID[tileDef.materialId] = tileDef
|
||||
tiles[tileDef.materialName] = tileDef
|
||||
} catch (err: Throwable) {
|
||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||
LOGGER.error("Loading tile file $listedFile", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -298,4 +297,10 @@ object Starbound : IVFS {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMaterialModifiers(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class StarboundClient : AutoCloseable {
|
||||
|
||||
val gl = GLStateTracker()
|
||||
|
||||
var world: ClientWorld? = ClientWorld(this, 0L, 94)
|
||||
var world: ClientWorld? = ClientWorld(this, 0L, 0)
|
||||
|
||||
fun ensureSameThread() = gl.ensureSameThread()
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
|
||||
|
||||
data class MaterialModifier(
|
||||
val modId: Int,
|
||||
val modName: String,
|
||||
val itemDrop: String,
|
||||
val description: String,
|
||||
val health: Int,
|
||||
val harvestLevel: Int,
|
||||
val breaksWithTile: Boolean,
|
||||
val miningSounds: List<String>,
|
||||
val miningParticle: String,
|
||||
val renderTemplate: String,
|
||||
) {
|
||||
companion object {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
|
||||
.plain(MaterialModifier::modId)
|
||||
.plain(MaterialModifier::modName)
|
||||
.plain(MaterialModifier::itemDrop)
|
||||
.plain(MaterialModifier::description)
|
||||
.plain(MaterialModifier::health)
|
||||
.plain(MaterialModifier::harvestLevel)
|
||||
.plain(MaterialModifier::breaksWithTile)
|
||||
.list(MaterialModifier::miningSounds, String::class.java)
|
||||
.plain(MaterialModifier::miningParticle)
|
||||
.plain(MaterialModifier::renderTemplate)
|
||||
.build()
|
||||
|
||||
fun registerGson(gsonBuilder: GsonBuilder) {
|
||||
gsonBuilder.registerTypeAdapter(MaterialModifier::class.java, ADAPTER)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,14 @@ package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.world.ITileGetter
|
||||
@ -482,6 +487,18 @@ data class TileRenderTemplate(
|
||||
companion object {
|
||||
val map = HashMap<String, TileRenderTemplate>()
|
||||
|
||||
fun register(builder: GsonBuilder) {
|
||||
builder.registerTypeAdapter(TileRenderTemplate::class.java, object : TypeAdapter<TileRenderTemplate>() {
|
||||
override fun write(out: JsonWriter?, value: TileRenderTemplate?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): TileRenderTemplate {
|
||||
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`) as JsonObject)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun load(path: String): TileRenderTemplate {
|
||||
return map.computeIfAbsent(path) {
|
||||
try {
|
||||
|
@ -0,0 +1,400 @@
|
||||
package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
import kotlin.reflect.full.isSupertypeOf
|
||||
|
||||
private class PassthroughAdapter<T : Any?>(private val bound: Class<T>) : TypeAdapter<T?>() {
|
||||
override fun write(out: JsonWriter, value: T?) {
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(value, bound) as JsonElement, out)
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): T? {
|
||||
return Starbound.gson.fromJson(reader, bound)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> resolveBound(bound: Class<T>): TypeAdapter<T>? {
|
||||
return when (bound) {
|
||||
Float::class.java -> TypeAdapters.FLOAT as TypeAdapter<T>
|
||||
Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter<T>
|
||||
String::class.java -> TypeAdapters.STRING as TypeAdapter<T>
|
||||
Int::class.java -> TypeAdapters.INTEGER as TypeAdapter<T>
|
||||
Long::class.java -> TypeAdapters.LONG as TypeAdapter<T>
|
||||
Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter<T>
|
||||
else -> Starbound.gson.getAdapter(bound)
|
||||
}
|
||||
}
|
||||
|
||||
class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
|
||||
private val resolvedBound by lazy {
|
||||
resolveBound(bound)
|
||||
}
|
||||
|
||||
override fun write(out: JsonWriter, value: List<T>) {
|
||||
out.beginArray()
|
||||
|
||||
val resolvedBound = resolvedBound
|
||||
|
||||
if (resolvedBound != null) {
|
||||
for (v in value) {
|
||||
resolvedBound.write(out, v)
|
||||
}
|
||||
} else {
|
||||
for (v in value) {
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
|
||||
}
|
||||
}
|
||||
|
||||
out.endArray()
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): List<T> {
|
||||
reader.beginArray()
|
||||
|
||||
val builder = ImmutableList.builder<T>()
|
||||
val resolvedBound = resolvedBound
|
||||
|
||||
if (resolvedBound != null) {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
|
||||
builder.add(readObject as T)
|
||||
}
|
||||
} else {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
val readObject = Starbound.gson.fromJson<T>(reader, bound) ?: throw JsonSyntaxException("List does not accept nulls")
|
||||
builder.add(readObject)
|
||||
}
|
||||
}
|
||||
|
||||
reader.endArray()
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: Class<V>) : TypeAdapter<Map<K, V>>() {
|
||||
private val resolvedKey by lazy {
|
||||
resolveBound(boundKey)
|
||||
}
|
||||
|
||||
private val resolvedValue by lazy {
|
||||
resolveBound(boundValue)
|
||||
}
|
||||
|
||||
override fun write(out: JsonWriter, value: Map<K, V>) {
|
||||
out.beginArray()
|
||||
|
||||
val resolvedKey = resolvedKey
|
||||
val resolvedValue = resolvedValue
|
||||
|
||||
if (resolvedKey != null && resolvedValue != null) {
|
||||
for ((k, v) in value) {
|
||||
out.beginArray()
|
||||
resolvedKey.write(out, k)
|
||||
resolvedValue.write(out, v)
|
||||
out.endArray()
|
||||
}
|
||||
} else if (resolvedKey != null) {
|
||||
for ((k, v) in value) {
|
||||
out.beginArray()
|
||||
resolvedKey.write(out, k)
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
|
||||
out.endArray()
|
||||
}
|
||||
} else if (resolvedValue != null) {
|
||||
for ((k, v) in value) {
|
||||
out.beginArray()
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
|
||||
resolvedValue.write(out, v)
|
||||
out.endArray()
|
||||
}
|
||||
} else {
|
||||
for ((k, v) in value) {
|
||||
out.beginArray()
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
|
||||
out.endArray()
|
||||
}
|
||||
}
|
||||
|
||||
out.endArray()
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): Map<K, V> {
|
||||
reader.beginArray()
|
||||
|
||||
val builder = ImmutableMap.builder<K, V>()
|
||||
|
||||
val resolvedKey = resolvedKey
|
||||
val resolvedValue = resolvedValue
|
||||
|
||||
if (resolvedKey != null && resolvedValue != null) {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
reader.beginArray()
|
||||
builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
|
||||
reader.endArray()
|
||||
}
|
||||
} else if (resolvedKey != null) {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
reader.beginArray()
|
||||
builder.put(resolvedKey.read(reader), Starbound.gson.fromJson(reader, boundValue))
|
||||
reader.endArray()
|
||||
}
|
||||
} else if (resolvedValue != null) {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
reader.beginArray()
|
||||
builder.put(Starbound.gson.fromJson(reader, boundKey), resolvedValue.read(reader))
|
||||
reader.endArray()
|
||||
}
|
||||
} else {
|
||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
||||
reader.beginArray()
|
||||
builder.put(Starbound.gson.fromJson(reader, boundKey), Starbound.gson.fromJson(reader, boundValue))
|
||||
reader.endArray()
|
||||
}
|
||||
}
|
||||
|
||||
reader.endArray()
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String, V>>() {
|
||||
private val resolvedBound by lazy {
|
||||
resolveBound(bound)
|
||||
}
|
||||
|
||||
override fun write(out: JsonWriter, value: Map<String, V>) {
|
||||
val resolvedBound = resolvedBound
|
||||
|
||||
out.beginObject()
|
||||
|
||||
if (resolvedBound != null) {
|
||||
for ((k, v) in value) {
|
||||
out.name(k)
|
||||
resolvedBound.write(out, v)
|
||||
}
|
||||
} else {
|
||||
for ((k, v) in value) {
|
||||
out.name(k)
|
||||
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
|
||||
}
|
||||
}
|
||||
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): Map<String, V> {
|
||||
val builder = ImmutableMap.builder<String, V>()
|
||||
|
||||
reader.beginObject()
|
||||
|
||||
val resolvedBound = resolvedBound
|
||||
|
||||
if (resolvedBound != null) {
|
||||
while (reader.peek() != JsonToken.END_OBJECT) {
|
||||
builder.put(reader.nextName(), resolvedBound.read(reader))
|
||||
}
|
||||
} else {
|
||||
while (reader.peek() != JsonToken.END_OBJECT) {
|
||||
builder.put(reader.nextName(), Starbound.gson.fromJson(reader, bound))
|
||||
}
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeAdapter для классов которые создаются единожды и более не меняются ("бетонных классов").
|
||||
*/
|
||||
class KConcreteTypeAdapter<T : Any>(
|
||||
val bound: KClass<T>,
|
||||
val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>
|
||||
) : TypeAdapter<T>() {
|
||||
private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
|
||||
private val mapped = Object2IntArrayMap<String>()
|
||||
|
||||
private val loggedMisses = ObjectArraySet<String>()
|
||||
|
||||
init {
|
||||
for ((field) in types) {
|
||||
returnTypeCache[field] = field.returnType
|
||||
}
|
||||
|
||||
mapped.defaultReturnValue(-1)
|
||||
|
||||
for ((i, pair) in types.withIndex()) {
|
||||
mapped[pair.first.name] = i
|
||||
}
|
||||
}
|
||||
|
||||
private val factory: KFunction<T> = bound.constructors.firstOrNull first@{
|
||||
if (it.parameters.size == types.size) {
|
||||
val iterator = types.iterator()
|
||||
|
||||
for (param in it.parameters) {
|
||||
val nextParam = iterator.next()
|
||||
|
||||
val a = param.type
|
||||
val b = nextParam.first.returnType
|
||||
|
||||
if (!a.isSupertypeOf(b) || a.isMarkedNullable != b.isMarkedNullable) {
|
||||
return@first false
|
||||
}
|
||||
}
|
||||
|
||||
return@first true
|
||||
}
|
||||
|
||||
return@first false
|
||||
} ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})")
|
||||
|
||||
override fun write(out: JsonWriter, value: T) {
|
||||
out.beginObject()
|
||||
|
||||
for ((field, adapter) in types) {
|
||||
out.name(field.name)
|
||||
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
|
||||
}
|
||||
|
||||
out.endObject()
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): T {
|
||||
reader.beginObject()
|
||||
|
||||
val readValues = arrayOfNulls<Any>(types.size)
|
||||
|
||||
while (reader.peek() != JsonToken.END_OBJECT) {
|
||||
val name = reader.nextName()
|
||||
val fieldId = mapped.getInt(name)
|
||||
|
||||
if (fieldId == -1) {
|
||||
if (loggedMisses.add(name)) {
|
||||
LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
|
||||
}
|
||||
|
||||
reader.skipValue()
|
||||
} else {
|
||||
val (field, adapter) = types[fieldId]
|
||||
|
||||
try {
|
||||
readValues[fieldId] = adapter.read(reader)
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Exception reading field ${field.name}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((i, pair) in types.withIndex()) {
|
||||
val (field) = pair
|
||||
|
||||
if (readValues[i] == null && !returnTypeCache[field]!!.isMarkedNullable) {
|
||||
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
|
||||
}
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
|
||||
return factory.call(*readValues as Array<out Any>)
|
||||
}
|
||||
|
||||
class Builder<T : Any>(val clazz: KClass<T>) {
|
||||
private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>()
|
||||
|
||||
/**
|
||||
* Добавляет поле без generic типов
|
||||
*/
|
||||
fun plain(field: KProperty1<T, *>): 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.isSuperclassOf(Float::class)) {
|
||||
types.add(field to TypeAdapters.FLOAT)
|
||||
} else if (classifier.isSuperclassOf(Double::class)) {
|
||||
types.add(field to TypeAdapters.DOUBLE)
|
||||
} else if (classifier.isSuperclassOf(Int::class)) {
|
||||
types.add(field to TypeAdapters.INTEGER)
|
||||
} else if (classifier.isSuperclassOf(Long::class)) {
|
||||
types.add(field to TypeAdapters.LONG)
|
||||
} else if (classifier.isSuperclassOf(String::class)) {
|
||||
types.add(field to TypeAdapters.STRING)
|
||||
} else if (classifier.isSuperclassOf(Boolean::class)) {
|
||||
types.add(field to TypeAdapters.BOOLEAN)
|
||||
} else {
|
||||
types.add(field to PassthroughAdapter(classifier.java))
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле, которое содержит список значений V (без null).
|
||||
*
|
||||
* Список неизменяем (создаётся объект [ImmutableList])
|
||||
*/
|
||||
fun <V> list(field: KProperty1<T, List<V>>, type: Class<V>): Builder<T> {
|
||||
types.add(field to ListAdapter(type))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле, которое содержит список значений V (без null).
|
||||
*
|
||||
* Список неизменяем (создаётся объект [ImmutableList])
|
||||
*/
|
||||
fun <V : Any> list(field: KProperty1<T, List<V>>, type: KClass<V>): Builder<T> {
|
||||
return this.list(field, type.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...]
|
||||
*
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <K, V> map(field: KProperty1<T, Map<K, V>>, keyType: Class<K>, valueType: Class<V>): Builder<T> {
|
||||
types.add(field to MapAdapter(keyType, valueType))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
|
||||
*
|
||||
* Таблица неизменяема (создаётся объект [ImmutableMap])
|
||||
*/
|
||||
fun <V> map(field: KProperty1<T, Map<String, V>>, valueType: Class<V>): Builder<T> {
|
||||
types.add(field to StringMapAdapter(valueType))
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): KConcreteTypeAdapter<T> {
|
||||
return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
@ -17,7 +18,14 @@ import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
|
||||
/**
|
||||
* Kotlin property aware adapter
|
||||
* Kotlin property aware adapter.
|
||||
*
|
||||
* Создаёт пустые классы, а после наполняет их данными, что подходит для builder'ов с очень
|
||||
* большим количеством возможных данных внутри.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Подходит для игровых структур которые могут быть "разобраны" и пересобраны.
|
||||
*/
|
||||
class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapter<T>() {
|
||||
private val mappedFields = Object2ObjectArrayMap<String, KMutableProperty1<T, in Any?>>()
|
||||
@ -35,9 +43,14 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
|
||||
}
|
||||
}
|
||||
|
||||
val fields: Array<KMutableProperty1<T, in Any?>> get() {
|
||||
val iterator = mappedFields.values.iterator()
|
||||
return Array(mappedFields.size) { iterator.next() }
|
||||
val fields: List<KMutableProperty1<T, in Any?>> by lazy {
|
||||
return@lazy ImmutableList.builder<KMutableProperty1<T, in Any?>>().let {
|
||||
for (v in mappedFields.values.iterator()) {
|
||||
it.add(v)
|
||||
}
|
||||
|
||||
it.build()
|
||||
}
|
||||
}
|
||||
|
||||
fun ignoreProperty(vararg value: String): KTypeAdapter<T> {
|
||||
@ -81,7 +94,7 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
|
||||
val expectedType = mappedFieldsReturnTypes[name]!!
|
||||
|
||||
if (!expectedType.isMarkedNullable && peek == JsonToken.NULL) {
|
||||
throw IllegalArgumentException("Property ${field.name} of ${instance::class.qualifiedName} does not accept nulls")
|
||||
throw NullPointerException("Property ${field.name} of ${instance::class.qualifiedName} does not accept nulls")
|
||||
} else if (peek == JsonToken.NULL) {
|
||||
field.set(instance, null)
|
||||
reader.nextNull()
|
||||
@ -108,8 +121,7 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
|
||||
val read = reader.nextBoolean()
|
||||
field.set(instance, read)
|
||||
} else {
|
||||
val readElement = TypeAdapters.JSON_ELEMENT.read(reader)
|
||||
field.set(instance, Starbound.gson.fromJson(readElement, classifier.java))
|
||||
field.set(instance, Starbound.gson.fromJson(reader, classifier.java))
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("Expected ${field.name} classifier to be KClass, got $classifier")
|
||||
@ -140,4 +152,4 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user