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
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import org.apache.logging.log4j.LogManager
import org.lwjgl.Version
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.defs.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.defs.tile.MaterialModifier
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.entities.Move
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.io.ByteArrayInputStream
import java.io.DataInputStream
import java.io.File
import java.io.InputStream
import java.util.*
import java.util.zip.Inflater
private val LOGGER = LogManager.getLogger()
@ -41,34 +27,6 @@ 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"))
@ -133,6 +91,34 @@ fun main() {
val ent = PlayerEntity(client.world!!)
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 set = 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.defs.*
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.dungeon.DungeonWorldDef
import ru.dbotthepony.kstarbound.io.*
@ -72,6 +75,8 @@ object Starbound : IVFS {
.also(ParallaxPrototype::registerGson)
.also(JsonFunction::registerGson)
.also(MaterialModifier::registerGson)
.also(RenderParameters::registerGson)
.also(RenderTemplate::registerGson)
.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 ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
@ -11,9 +11,10 @@ data class MaterialModifier(
val health: Int,
val harvestLevel: Int,
val breaksWithTile: Boolean,
val miningSounds: List<String>,
val miningParticle: String,
val renderTemplate: String,
val miningSounds: List<String> = listOf(),
val miningParticle: String? = null,
val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
@ -26,7 +27,8 @@ data class MaterialModifier(
.plain(MaterialModifier::breaksWithTile)
.list(MaterialModifier::miningSounds, String::class.java)
.plain(MaterialModifier::miningParticle)
.plain(MaterialModifier::renderTemplate)
.plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE)
.plain(MaterialModifier::renderParameters)
.build()
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.ImmutableMap
import com.google.gson.JsonElement
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
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 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 java.lang.reflect.Constructor
import kotlin.jvm.internal.DefaultConstructorMarker
import kotlin.reflect.*
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.isSupertypeOf
import kotlin.reflect.full.memberProperties
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>? {
@Suppress("unchecked_cast")
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>
@ -53,14 +44,8 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
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)
}
for (v in value) {
resolvedBound.write(out, v)
}
out.endArray()
@ -72,16 +57,9 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
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)
}
while (reader.peek() != JsonToken.END_ARRAY) {
val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
builder.add(readObject as T)
}
reader.endArray()
@ -105,34 +83,11 @@ class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: C
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()
}
for ((k, v) in value) {
out.beginArray()
resolvedKey.write(out, k)
resolvedValue.write(out, v)
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 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()
}
while (reader.peek() != JsonToken.END_ARRAY) {
reader.beginArray()
builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
reader.endArray()
}
reader.endArray()
@ -188,16 +123,9 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
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)
}
for ((k, v) in value) {
out.name(k)
resolvedBound.write(out, v)
}
out.endObject()
@ -210,14 +138,8 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
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))
}
while (reader.peek() != JsonToken.END_OBJECT) {
builder.put(reader.nextName(), resolvedBound.read(reader))
}
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 для классов которые создаются единожды и более не меняются ("бетонных классов").
*/
class KConcreteTypeAdapter<T : Any>(
val bound: KClass<T>,
val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>
val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>,
val asList: Boolean = false
) : TypeAdapter<T>() {
fun register(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(bound.java, this)
}
private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
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) {
val iterator = types.iterator()
@ -271,11 +210,50 @@ class KConcreteTypeAdapter<T : Any>(
return@first false
} ?: 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) {
out.beginObject()
for ((field, adapter) in types) {
out.name(field.name)
@Suppress("unchecked_cast")
(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 {
reader.beginObject()
if (asList) {
reader.beginArray()
} else {
reader.beginObject()
}
val presentValues = BooleanArray(types.size)
val readValues = arrayOfNulls<Any>(types.size)
while (reader.peek() != JsonToken.END_OBJECT) {
val name = reader.nextName()
val fieldId = mapped.getInt(name)
if (asList) {
val iterator = types.iterator()
var fieldId = 0
if (fieldId == -1) {
if (loggedMisses.add(name)) {
LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
while (reader.peek() != JsonToken.END_ARRAY) {
if (!iterator.hasNext()) {
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()
} else {
val (field, adapter) = types[fieldId]
val (field, adapter) = iterator.next()
try {
readValues[fieldId] = adapter.read(reader)
presentValues[fieldId] = true
} catch(err: Throwable) {
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()) {
val (field) = pair
if (readValues[i] == null && !returnTypeCache[field]!!.isMarkedNullable) {
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
for (i in readValues.indices) {
if (readValues[i] is String) {
readValues[i] = (readValues[i] as String).intern()
}
}
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>) {
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}!")
fun <V> plain(field: KProperty1<T, V>, adapter: TypeAdapter<V>): Builder<T> {
types.add(field to adapter)
return this
}
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))
/**
* Добавляет поле(я) без generic типов и без преобразователей
*/
fun plain(vararg fields: KProperty1<T, *>): Builder<T> {
for (field in fields) {
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 LazyTypeProvider(classifier.java))
}
}
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).
*
* Список неизменяем (создаётся объект [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))
return this
}
@ -365,7 +475,7 @@ class KConcreteTypeAdapter<T : Any>(
*
* Список неизменяем (создаётся объект [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)
}
@ -379,6 +489,16 @@ class KConcreteTypeAdapter<T : Any>(
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, ...}
*
@ -389,8 +509,18 @@ class KConcreteTypeAdapter<T : Any>(
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)
}
}