KConcreteTypeAdapter functional
This commit is contained in:
parent
e2b17f5761
commit
d3396ddb7c
@ -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
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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) {
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user