KConcreteTypeAdapter functional

This commit is contained in:
DBotThePony 2022-08-26 20:44:00 +07:00
parent e2b17f5761
commit d3396ddb7c
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 528 additions and 193 deletions

View File

@ -1,31 +1,17 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
import com.google.gson.JsonElement
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
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
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.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.TileDefinition
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.entities.Move
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.DataInputStream import java.io.DataInputStream
import java.io.File import java.io.File
import java.io.InputStream
import java.util.*
import java.util.zip.Inflater import java.util.zip.Inflater
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
@ -41,34 +27,6 @@ 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"))
@ -133,6 +91,34 @@ fun main() {
val ent = PlayerEntity(client.world!!) val ent = PlayerEntity(client.world!!)
Starbound.onInitialize { Starbound.onInitialize {
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@onInitialize
}
var find = 0L var find = 0L
var set = 0L var set = 0L
var parse = 0L var parse = 0L

View File

@ -9,6 +9,9 @@ import ru.dbotthepony.kstarbound.api.getPathFilename
import ru.dbotthepony.kstarbound.api.getPathFolder import ru.dbotthepony.kstarbound.api.getPathFolder
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.projectile.*
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate
import ru.dbotthepony.kstarbound.defs.world.SkyParameters import ru.dbotthepony.kstarbound.defs.world.SkyParameters
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
@ -72,6 +75,8 @@ object Starbound : IVFS {
.also(ParallaxPrototype::registerGson) .also(ParallaxPrototype::registerGson)
.also(JsonFunction::registerGson) .also(JsonFunction::registerGson)
.also(MaterialModifier::registerGson) .also(MaterialModifier::registerGson)
.also(RenderParameters::registerGson)
.also(RenderTemplate::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.kstarbound.defs package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
@ -11,9 +11,10 @@ data class MaterialModifier(
val health: Int, val health: Int,
val harvestLevel: Int, val harvestLevel: Int,
val breaksWithTile: Boolean, val breaksWithTile: Boolean,
val miningSounds: List<String>, val miningSounds: List<String> = listOf(),
val miningParticle: String, val miningParticle: String? = null,
val renderTemplate: String, val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters
) { ) {
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class) val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
@ -26,7 +27,8 @@ data class MaterialModifier(
.plain(MaterialModifier::breaksWithTile) .plain(MaterialModifier::breaksWithTile)
.list(MaterialModifier::miningSounds, String::class.java) .list(MaterialModifier::miningSounds, String::class.java)
.plain(MaterialModifier::miningParticle) .plain(MaterialModifier::miningParticle)
.plain(MaterialModifier::renderTemplate) .plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE)
.plain(MaterialModifier::renderParameters)
.build() .build()
fun registerGson(gsonBuilder: GsonBuilder) { fun registerGson(gsonBuilder: GsonBuilder) {

View File

@ -0,0 +1,26 @@
package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
data class RenderParameters(
val texture: String,
val variants: Int,
val multiColored: Boolean,
val zLevel: Int,
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class)
.plain(
RenderParameters::texture,
RenderParameters::variants,
RenderParameters::multiColored,
RenderParameters::zLevel,
)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(RenderParameters::class.java, ADAPTER)
}
}
}

View File

@ -0,0 +1,186 @@
package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.TileDefinition
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap
data class RenderPiece(
val texture: String? = null,
val textureSize: Vector2i,
val texturePosition: Vector2i,
val colorStride: Vector2i? = null,
val variantStride: Vector2i? = null,
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderPiece::class)
.plain(
RenderPiece::texture,
RenderPiece::textureSize,
RenderPiece::texturePosition,
RenderPiece::colorStride,
RenderPiece::variantStride,
)
.build()
}
}
data class RenderRule(
val type: String,
val matchHue: Boolean = false,
val inverse: Boolean = false,
) {
private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) {
"EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef
"Connects" -> getter[thisPos + offsetPos] != null
else -> false
}
}
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) {
return !doTest(getter, thisRef, thisPos, offsetPos)
}
return doTest(getter, thisRef, thisPos, offsetPos)
}
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderRule::class)
.plain(
RenderRule::type,
RenderRule::matchHue,
RenderRule::inverse,
)
.build()
}
}
data class RenderRuleList(
val entries: List<RenderRule>
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class)
.list(RenderRuleList::entries, RenderRule::class)
.build()
}
}
data class RenderMatch(
val pieces: List<Piece>? = null,
val matchAllPoints: List<Matcher>? = null,
val subMatches: List<RenderMatch>? = null,
val haltOnMatch: Boolean = false
) {
data class Piece(
val name: String,
val point: Vector2i
)
data class Matcher(
val point: Vector2i,
val ruleName: String
)
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class)
.list(RenderMatch::pieces, Piece::class)
.list(RenderMatch::matchAllPoints, Matcher::class)
.list(RenderMatch::subMatches, RenderMatch::class)
.plain(RenderMatch::haltOnMatch)
.build()
val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class)
.plain(Piece::name)
.plain(Piece::point)
.build(asList = true)
val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class)
.plain(Matcher::point)
.plain(Matcher::ruleName)
.build(asList = true)
}
}
data class RenderMatchList(
val name: String,
val list: List<RenderMatch>
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class)
.plain(RenderMatchList::name)
.list(RenderMatchList::list, RenderMatch::class)
.build(asList = true)
}
}
data class RenderTemplate(
val pieces: Map<String, RenderPiece>,
val representativePiece: String,
val matches: List<RenderMatchList>
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class)
.map(RenderTemplate::pieces, RenderPiece::class.java)
.plain(RenderTemplate::representativePiece)
.list(RenderTemplate::matches, RenderMatchList::class.java)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
RenderPiece.ADAPTER.register(gsonBuilder)
RenderRule.ADAPTER.register(gsonBuilder)
RenderRuleList.ADAPTER.register(gsonBuilder)
RenderMatch.ADAPTER.register(gsonBuilder)
RenderMatch.PIECE_ADAPTER.register(gsonBuilder)
RenderMatch.MATCHER_ADAPTER.register(gsonBuilder)
RenderMatchList.ADAPTER.register(gsonBuilder)
ADAPTER.register(gsonBuilder)
}
private val cache = ConcurrentHashMap<String, RenderTemplate>()
private val _readingFolder = ThreadLocal<String>()
/**
* Случит переменной для указания из какой папки происходит чтение в данном потоке
*/
var readingFolder: String?
get() = _readingFolder.get()
set(value) { _readingFolder.set(value) }
val CACHE = object : TypeAdapter<RenderTemplate>() {
override fun write(out: JsonWriter, value: RenderTemplate) {
ADAPTER.write(out, value)
}
override fun read(reader: JsonReader): RenderTemplate {
if (reader.peek() != JsonToken.STRING) {
throw JsonSyntaxException("Expected string as input for render template cache retriever")
}
var path = reader.nextString()
if (path[0] != '/') {
// относительный путь
val readingFolder = readingFolder ?: throw NullPointerException("Currently read folder is not specified")
path = "$readingFolder/$path"
}
return cache.computeIfAbsent(path) {
return@computeIfAbsent Starbound.gson.fromJson(Starbound.getReader(it), RenderTemplate::class.java)
}
}
}
}
}

View File

@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.io
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.JsonElement import com.google.gson.GsonBuilder
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
@ -14,24 +14,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import kotlin.reflect.KClass import java.lang.reflect.Constructor
import kotlin.reflect.KFunction import kotlin.jvm.internal.DefaultConstructorMarker
import kotlin.reflect.KProperty1 import kotlin.reflect.*
import kotlin.reflect.KType
import kotlin.reflect.full.isSuperclassOf import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.isSupertypeOf import kotlin.reflect.full.isSupertypeOf
import kotlin.reflect.full.memberProperties
private class PassthroughAdapter<T : Any?>(private val bound: Class<T>) : TypeAdapter<T?>() { @Suppress("unchecked_cast")
override fun write(out: JsonWriter, value: T?) { private fun <T> resolveBound(bound: Class<T>): TypeAdapter<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) { return when (bound) {
Float::class.java -> TypeAdapters.FLOAT as TypeAdapter<T> Float::class.java -> TypeAdapters.FLOAT as TypeAdapter<T>
Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter<T> Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter<T>
@ -53,14 +44,8 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
val resolvedBound = resolvedBound val resolvedBound = resolvedBound
if (resolvedBound != null) { for (v in value) {
for (v in value) { resolvedBound.write(out, v)
resolvedBound.write(out, v)
}
} else {
for (v in value) {
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
}
} }
out.endArray() out.endArray()
@ -72,16 +57,9 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
val builder = ImmutableList.builder<T>() val builder = ImmutableList.builder<T>()
val resolvedBound = resolvedBound val resolvedBound = resolvedBound
if (resolvedBound != null) { while (reader.peek() != JsonToken.END_ARRAY) {
while (reader.peek() != JsonToken.END_ARRAY) { val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls") builder.add(readObject as T)
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() reader.endArray()
@ -105,34 +83,11 @@ class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: C
val resolvedKey = resolvedKey val resolvedKey = resolvedKey
val resolvedValue = resolvedValue val resolvedValue = resolvedValue
if (resolvedKey != null && resolvedValue != null) { for ((k, v) in value) {
for ((k, v) in value) { out.beginArray()
out.beginArray() resolvedKey.write(out, k)
resolvedKey.write(out, k) resolvedValue.write(out, v)
resolvedValue.write(out, v) out.endArray()
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() out.endArray()
@ -146,30 +101,10 @@ class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: C
val resolvedKey = resolvedKey val resolvedKey = resolvedKey
val resolvedValue = resolvedValue val resolvedValue = resolvedValue
if (resolvedKey != null && resolvedValue != null) { while (reader.peek() != JsonToken.END_ARRAY) {
while (reader.peek() != JsonToken.END_ARRAY) { reader.beginArray()
reader.beginArray() builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
builder.put(resolvedKey.read(reader), resolvedValue.read(reader)) reader.endArray()
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() reader.endArray()
@ -188,16 +123,9 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
out.beginObject() out.beginObject()
if (resolvedBound != null) { for ((k, v) in value) {
for ((k, v) in value) { out.name(k)
out.name(k) resolvedBound.write(out, v)
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() out.endObject()
@ -210,14 +138,8 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
val resolvedBound = resolvedBound val resolvedBound = resolvedBound
if (resolvedBound != null) { while (reader.peek() != JsonToken.END_OBJECT) {
while (reader.peek() != JsonToken.END_OBJECT) { builder.put(reader.nextName(), resolvedBound.read(reader))
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() reader.endObject()
@ -226,13 +148,30 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
} }
} }
private class LazyTypeProvider<T : Any?>(private val bound: Class<T>) : TypeAdapter<T>() {
private val resolved by lazy { resolveBound(bound) }
override fun write(out: JsonWriter, value: T) {
resolved.write(out, value)
}
override fun read(`in`: JsonReader): T {
return resolved.read(`in`)
}
}
/** /**
* TypeAdapter для классов которые создаются единожды и более не меняются ("бетонных классов"). * TypeAdapter для классов которые создаются единожды и более не меняются ("бетонных классов").
*/ */
class KConcreteTypeAdapter<T : Any>( class KConcreteTypeAdapter<T : Any>(
val bound: KClass<T>, val bound: KClass<T>,
val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>> val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>,
val asList: Boolean = false
) : TypeAdapter<T>() { ) : TypeAdapter<T>() {
fun register(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(bound.java, this)
}
private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>() private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
private val mapped = Object2IntArrayMap<String>() private val mapped = Object2IntArrayMap<String>()
@ -250,7 +189,7 @@ class KConcreteTypeAdapter<T : Any>(
} }
} }
private val factory: KFunction<T> = bound.constructors.firstOrNull first@{ private val regularFactory: KFunction<T> = bound.constructors.firstOrNull first@{
if (it.parameters.size == types.size) { if (it.parameters.size == types.size) {
val iterator = types.iterator() val iterator = types.iterator()
@ -271,11 +210,50 @@ class KConcreteTypeAdapter<T : Any>(
return@first false return@first false
} ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})") } ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})")
private val syntheticFactory: Constructor<T>? = try {
bound.java.getDeclaredConstructor(*types.map { (it.first.returnType.classifier as KClass<*>).java }.also {
it as MutableList
it.add(Int::class.java)
it.add(DefaultConstructorMarker::class.java)
}.toTypedArray())
} catch(_: NoSuchMethodException) {
null
}
private val syntheticPrimitives: Array<Any?>?
init {
if (syntheticFactory == null) {
syntheticPrimitives = null
} else {
syntheticPrimitives = arrayOfNulls(syntheticFactory.parameters.size)
for ((i, param) in syntheticFactory.parameters.withIndex()) {
val type = param.parameterizedType as? Class<*> ?: continue
if (type.isPrimitive) {
syntheticPrimitives[i] = when (type.name) {
"boolean" -> false
"int" -> 0
"long" -> 0L
"short" -> (0).toShort()
"byte" -> (0).toByte()
"char" -> (0).toChar()
"float" -> 0f
"double" -> 0.0
else -> throw IllegalArgumentException("mamma mia: ${type.name}")
}
}
}
}
}
override fun write(out: JsonWriter, value: T) { override fun write(out: JsonWriter, value: T) {
out.beginObject() out.beginObject()
for ((field, adapter) in types) { for ((field, adapter) in types) {
out.name(field.name) out.name(field.name)
@Suppress("unchecked_cast")
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value)) (adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
} }
@ -283,79 +261,211 @@ class KConcreteTypeAdapter<T : Any>(
} }
override fun read(reader: JsonReader): T { override fun read(reader: JsonReader): T {
reader.beginObject() if (asList) {
reader.beginArray()
} else {
reader.beginObject()
}
val presentValues = BooleanArray(types.size)
val readValues = arrayOfNulls<Any>(types.size) val readValues = arrayOfNulls<Any>(types.size)
while (reader.peek() != JsonToken.END_OBJECT) { if (asList) {
val name = reader.nextName() val iterator = types.iterator()
val fieldId = mapped.getInt(name) var fieldId = 0
if (fieldId == -1) { while (reader.peek() != JsonToken.END_ARRAY) {
if (loggedMisses.add(name)) { if (!iterator.hasNext()) {
LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field") val name = fieldId.toString()
if (loggedMisses.add(name)) {
LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
}
reader.skipValue()
fieldId++
continue
} }
reader.skipValue() val (field, adapter) = iterator.next()
} else {
val (field, adapter) = types[fieldId]
try { try {
readValues[fieldId] = adapter.read(reader) readValues[fieldId] = adapter.read(reader)
presentValues[fieldId] = true
} catch(err: Throwable) { } catch(err: Throwable) {
throw JsonSyntaxException("Exception reading field ${field.name}", err) throw JsonSyntaxException("Exception reading field ${field.name}", err)
} }
fieldId++
}
} else {
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)
presentValues[fieldId] = true
} catch(err: Throwable) {
throw JsonSyntaxException("Exception reading field ${field.name}", err)
}
}
} }
} }
for ((i, pair) in types.withIndex()) { for (i in readValues.indices) {
val (field) = pair if (readValues[i] is String) {
readValues[i] = (readValues[i] as String).intern()
if (readValues[i] == null && !returnTypeCache[field]!!.isMarkedNullable) {
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
} }
} }
reader.endObject() if (asList) {
reader.endArray()
} else {
reader.endObject()
}
return factory.call(*readValues as Array<out Any>) if (syntheticFactory == null || presentValues.all { it }) {
for ((i, pair) in types.withIndex()) {
val (field) = pair
if (readValues[i] == null) {
if (!returnTypeCache[field]!!.isMarkedNullable) {
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
}
if (!regularFactory.parameters[i].isOptional && !presentValues[i]) {
throw JsonSyntaxException("Field ${field.name} must be defined (even just as null)")
}
}
}
@Suppress("unchecked_cast")
return regularFactory.call(*readValues as Array<out Any>)
} else {
val ints = if (presentValues.size % 31 == 0) presentValues.size / 31 else presentValues.size / 31 + 1
val copied = readValues.copyOf(readValues.size + ints + 1)
var intIndex = readValues.size
var target = 0
var targetMove = 0
for (bool in presentValues) {
if (!bool) {
target = target.or(1.shl(targetMove))
}
targetMove++
if (targetMove >= 32) {
copied[intIndex++] = target
target = 0
targetMove = 0
}
}
if (targetMove != 0) {
copied[intIndex] = target
}
val syntheticPrimitives = syntheticPrimitives!!
for ((i, pair) in types.withIndex()) {
if (copied[i] != null) {
continue
}
val (field) = pair
val param = regularFactory.parameters[i]
if (!param.isOptional && !presentValues[i]) {
throw JsonSyntaxException("Field ${field.name} is missing")
}
if (returnTypeCache[field]!!.isMarkedNullable) {
continue
}
if (param.isOptional && !presentValues[i]) {
copied[i] = syntheticPrimitives[i] ?: throw NullPointerException("HOW $i")
continue
}
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
}
return syntheticFactory.newInstance(*copied)
}
} }
class Builder<T : Any>(val clazz: KClass<T>) { class Builder<T : Any>(val clazz: KClass<T>) {
private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>() private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>()
/** /**
* Добавляет поле без generic типов * Добавляет поле с определённым адаптером
*/ */
fun plain(field: KProperty1<T, *>): Builder<T> { fun <V> plain(field: KProperty1<T, V>, adapter: TypeAdapter<V>): Builder<T> {
val returnType = field.returnType types.add(field to adapter)
val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!") return this
}
if (classifier.isSuperclassOf(Float::class)) { /**
types.add(field to TypeAdapters.FLOAT) * Добавляет поле(я) без generic типов и без преобразователей
} else if (classifier.isSuperclassOf(Double::class)) { */
types.add(field to TypeAdapters.DOUBLE) fun plain(vararg fields: KProperty1<T, *>): Builder<T> {
} else if (classifier.isSuperclassOf(Int::class)) { for (field in fields) {
types.add(field to TypeAdapters.INTEGER) val returnType = field.returnType
} else if (classifier.isSuperclassOf(Long::class)) { val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!")
types.add(field to TypeAdapters.LONG)
} else if (classifier.isSuperclassOf(String::class)) { if (classifier.isSuperclassOf(Float::class)) {
types.add(field to TypeAdapters.STRING) types.add(field to TypeAdapters.FLOAT)
} else if (classifier.isSuperclassOf(Boolean::class)) { } else if (classifier.isSuperclassOf(Double::class)) {
types.add(field to TypeAdapters.BOOLEAN) types.add(field to TypeAdapters.DOUBLE)
} else { } else if (classifier.isSuperclassOf(Int::class)) {
types.add(field to PassthroughAdapter(classifier.java)) 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 LazyTypeProvider(classifier.java))
}
} }
return this return this
} }
/**
* Добавляет поле(я) без generic типов.
*
* Если поле с данным именем не найдено, кидается [NoSuchElementException]
*/
fun plain(vararg fields: String): Builder<T> {
val members = clazz.memberProperties
return this.plain(*Array(fields.size) {
val field = fields[it]
return@Array members.firstOrNull { it.name == field } ?: throw NoSuchElementException("Unable to find field $field")
})
}
/** /**
* Добавляет поле, которое содержит список значений V (без null). * Добавляет поле, которое содержит список значений V (без null).
* *
* Список неизменяем (создаётся объект [ImmutableList]) * Список неизменяем (создаётся объект [ImmutableList])
*/ */
fun <V> list(field: KProperty1<T, List<V>>, type: Class<V>): Builder<T> { fun <V : Any> list(field: KProperty1<T, List<V>?>, type: Class<V>): Builder<T> {
types.add(field to ListAdapter(type)) types.add(field to ListAdapter(type))
return this return this
} }
@ -365,7 +475,7 @@ class KConcreteTypeAdapter<T : Any>(
* *
* Список неизменяем (создаётся объект [ImmutableList]) * Список неизменяем (создаётся объект [ImmutableList])
*/ */
fun <V : Any> list(field: KProperty1<T, List<V>>, type: KClass<V>): Builder<T> { fun <V : Any> list(field: KProperty1<T, List<V>?>, type: KClass<V>): Builder<T> {
return this.list(field, type.java) return this.list(field, type.java)
} }
@ -379,6 +489,16 @@ class KConcreteTypeAdapter<T : Any>(
return this return this
} }
/**
* Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...]
*
* Таблица неизменяема (создаётся объект [ImmutableMap])
*/
fun <K : Any, V : Any> map(field: KProperty1<T, Map<K, V>>, keyType: KClass<K>, valueType: KClass<V>): Builder<T> {
types.add(field to MapAdapter(keyType.java, valueType.java))
return this
}
/** /**
* Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...} * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
* *
@ -389,8 +509,18 @@ class KConcreteTypeAdapter<T : Any>(
return this return this
} }
fun build(): KConcreteTypeAdapter<T> { /**
return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types)) * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
*
* Таблица неизменяема (создаётся объект [ImmutableMap])
*/
fun <V : Any> map(field: KProperty1<T, Map<String, V>>, valueType: KClass<V>): Builder<T> {
types.add(field to StringMapAdapter(valueType.java))
return this
}
fun build(asList: Boolean = false): KConcreteTypeAdapter<T> {
return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types), asList = asList)
} }
} }