KConcreteTypeAdapter test
This commit is contained in:
parent
0866edcb74
commit
e2b17f5761
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.Version
|
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.CircleShape
|
||||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.defs.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
|
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
|
||||||
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
|
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
|
||||||
@ -39,6 +41,34 @@ fun main() {
|
|||||||
//return
|
//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("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
|
||||||
//val db = BTreeDB(File("world.world"))
|
//val db = BTreeDB(File("world.world"))
|
||||||
|
|
||||||
|
@ -71,6 +71,7 @@ object Starbound : IVFS {
|
|||||||
.also(DungeonWorldDef::registerGson)
|
.also(DungeonWorldDef::registerGson)
|
||||||
.also(ParallaxPrototype::registerGson)
|
.also(ParallaxPrototype::registerGson)
|
||||||
.also(JsonFunction::registerGson)
|
.also(JsonFunction::registerGson)
|
||||||
|
.also(MaterialModifier::registerGson)
|
||||||
|
|
||||||
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
||||||
|
|
||||||
@ -226,21 +227,19 @@ object Starbound : IVFS {
|
|||||||
|
|
||||||
private fun loadTileMaterials(callback: (String) -> Unit) {
|
private fun loadTileMaterials(callback: (String) -> Unit) {
|
||||||
for (fs in fileSystems) {
|
for (fs in fileSystems) {
|
||||||
for (listedFile in fs.listAllFiles("tiles/materials")) {
|
for (listedFile in fs.listAllFilesWithExtension("material")) {
|
||||||
if (listedFile.endsWith(".material")) {
|
try {
|
||||||
try {
|
callback("Loading $listedFile")
|
||||||
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(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!" }
|
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
|
||||||
tilesByMaterialID[tileDef.materialId] = tileDef
|
tilesByMaterialID[tileDef.materialId] = tileDef
|
||||||
tiles[tileDef.materialName] = tileDef
|
tiles[tileDef.materialName] = tileDef
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
//throw TileDefLoadingException("Loading tile file $listedFile", err)
|
||||||
LOGGER.error("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()
|
val gl = GLStateTracker()
|
||||||
|
|
||||||
var world: ClientWorld? = ClientWorld(this, 0L, 94)
|
var world: ClientWorld? = ClientWorld(this, 0L, 0)
|
||||||
|
|
||||||
fun ensureSameThread() = gl.ensureSameThread()
|
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.ImmutableList
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
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 org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.world.ITileGetter
|
import ru.dbotthepony.kstarbound.world.ITileGetter
|
||||||
@ -482,6 +487,18 @@ data class TileRenderTemplate(
|
|||||||
companion object {
|
companion object {
|
||||||
val map = HashMap<String, TileRenderTemplate>()
|
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 {
|
fun load(path: String): TileRenderTemplate {
|
||||||
return map.computeIfAbsent(path) {
|
return map.computeIfAbsent(path) {
|
||||||
try {
|
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
|
package ru.dbotthepony.kstarbound.io
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.internal.bind.TypeAdapters
|
import com.google.gson.internal.bind.TypeAdapters
|
||||||
@ -17,7 +18,14 @@ import kotlin.reflect.KType
|
|||||||
import kotlin.reflect.full.isSuperclassOf
|
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>() {
|
class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapter<T>() {
|
||||||
private val mappedFields = Object2ObjectArrayMap<String, KMutableProperty1<T, in Any?>>()
|
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 fields: List<KMutableProperty1<T, in Any?>> by lazy {
|
||||||
val iterator = mappedFields.values.iterator()
|
return@lazy ImmutableList.builder<KMutableProperty1<T, in Any?>>().let {
|
||||||
return Array(mappedFields.size) { iterator.next() }
|
for (v in mappedFields.values.iterator()) {
|
||||||
|
it.add(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ignoreProperty(vararg value: String): KTypeAdapter<T> {
|
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]!!
|
val expectedType = mappedFieldsReturnTypes[name]!!
|
||||||
|
|
||||||
if (!expectedType.isMarkedNullable && peek == JsonToken.NULL) {
|
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) {
|
} else if (peek == JsonToken.NULL) {
|
||||||
field.set(instance, null)
|
field.set(instance, null)
|
||||||
reader.nextNull()
|
reader.nextNull()
|
||||||
@ -108,8 +121,7 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
|
|||||||
val read = reader.nextBoolean()
|
val read = reader.nextBoolean()
|
||||||
field.set(instance, read)
|
field.set(instance, read)
|
||||||
} else {
|
} else {
|
||||||
val readElement = TypeAdapters.JSON_ELEMENT.read(reader)
|
field.set(instance, Starbound.gson.fromJson(reader, classifier.java))
|
||||||
field.set(instance, Starbound.gson.fromJson(readElement, classifier.java))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw IllegalStateException("Expected ${field.name} classifier to be KClass, got $classifier")
|
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 {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
|
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user