KConcreteTypeAdapter test

This commit is contained in:
DBotThePony 2022-08-26 16:29:37 +07:00
parent 0866edcb74
commit e2b17f5761
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 522 additions and 22 deletions

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.Version import org.lwjgl.Version
@ -8,6 +9,7 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.defs.MaterialModifier
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.TileDefinition
import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
@ -39,6 +41,34 @@ fun main() {
//return //return
} }
if (true) {
val input = "{\n" +
" \"modId\" : 26,\n" +
" \"modName\" : \"aegisalt\",\n" +
" \"itemDrop\" : \"aegisaltore\",\n" +
" \"description\" : \"Aegisalt.\",\n" +
" \"health\" : 5,\n" +
" \"harvestLevel\" : 5,\n" +
" \"breaksWithTile\" : true,\n" +
"\n" +
" \"miningSounds\" : [ \"/sfx/tools/pickaxe_ore.ogg\", \"/sfx/tools/pickaxe_ore2.ogg\" ],\n" +
" \"miningParticle\" : \"orespark\",\n" +
"\n" +
" \"renderTemplate\" : \"/tiles/classicmaterialtemplate.config\",\n" +
" \"renderParameters\" : {\n" +
" \"texture\" : \"aegisalt.png\",\n" +
" \"variants\" : 8,\n" +
" \"multiColored\" : false,\n" +
" \"zLevel\" : 0\n" +
" }\n" +
"}\n"
val json = Starbound.gson.fromJson(input, MaterialModifier::class.java)
println(json)
return
}
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world")) val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
//val db = BTreeDB(File("world.world")) //val db = BTreeDB(File("world.world"))

View File

@ -71,6 +71,7 @@ object Starbound : IVFS {
.also(DungeonWorldDef::registerGson) .also(DungeonWorldDef::registerGson)
.also(ParallaxPrototype::registerGson) .also(ParallaxPrototype::registerGson)
.also(JsonFunction::registerGson) .also(JsonFunction::registerGson)
.also(MaterialModifier::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
@ -226,21 +227,19 @@ object Starbound : IVFS {
private fun loadTileMaterials(callback: (String) -> Unit) { private fun loadTileMaterials(callback: (String) -> Unit) {
for (fs in fileSystems) { for (fs in fileSystems) {
for (listedFile in fs.listAllFiles("tiles/materials")) { for (listedFile in fs.listAllFilesWithExtension("material")) {
if (listedFile.endsWith(".material")) { try {
try { callback("Loading $listedFile")
callback("Loading $listedFile")
val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials") val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials")
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" } check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
tilesByMaterialID[tileDef.materialId] = tileDef tilesByMaterialID[tileDef.materialId] = tileDef
tiles[tileDef.materialName] = tileDef tiles[tileDef.materialName] = tileDef
} catch (err: Throwable) { } catch (err: Throwable) {
//throw TileDefLoadingException("Loading tile file $listedFile", err) //throw TileDefLoadingException("Loading tile file $listedFile", err)
LOGGER.error("Loading tile file $listedFile", err) LOGGER.error("Loading tile file $listedFile", err)
}
} }
} }
} }
@ -298,4 +297,10 @@ object Starbound : IVFS {
} }
} }
} }
private fun loadMaterialModifiers(callback: (String) -> Unit) {
for (fs in fileSystems) {
}
}
} }

View File

@ -125,7 +125,7 @@ class StarboundClient : AutoCloseable {
val gl = GLStateTracker() val gl = GLStateTracker()
var world: ClientWorld? = ClientWorld(this, 0L, 94) var world: ClientWorld? = ClientWorld(this, 0L, 0)
fun ensureSameThread() = gl.ensureSameThread() fun ensureSameThread() = gl.ensureSameThread()

View File

@ -0,0 +1,36 @@
package ru.dbotthepony.kstarbound.defs
import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
data class MaterialModifier(
val modId: Int,
val modName: String,
val itemDrop: String,
val description: String,
val health: Int,
val harvestLevel: Int,
val breaksWithTile: Boolean,
val miningSounds: List<String>,
val miningParticle: String,
val renderTemplate: String,
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
.plain(MaterialModifier::modId)
.plain(MaterialModifier::modName)
.plain(MaterialModifier::itemDrop)
.plain(MaterialModifier::description)
.plain(MaterialModifier::health)
.plain(MaterialModifier::harvestLevel)
.plain(MaterialModifier::breaksWithTile)
.list(MaterialModifier::miningSounds, String::class.java)
.plain(MaterialModifier::miningParticle)
.plain(MaterialModifier::renderTemplate)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(MaterialModifier::class.java, ADAPTER)
}
}
}

View File

@ -2,9 +2,14 @@ package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kstarbound.world.ITileGetter
@ -482,6 +487,18 @@ data class TileRenderTemplate(
companion object { companion object {
val map = HashMap<String, TileRenderTemplate>() val map = HashMap<String, TileRenderTemplate>()
fun register(builder: GsonBuilder) {
builder.registerTypeAdapter(TileRenderTemplate::class.java, object : TypeAdapter<TileRenderTemplate>() {
override fun write(out: JsonWriter?, value: TileRenderTemplate?) {
TODO("Not yet implemented")
}
override fun read(`in`: JsonReader): TileRenderTemplate {
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`) as JsonObject)
}
})
}
fun load(path: String): TileRenderTemplate { fun load(path: String): TileRenderTemplate {
return map.computeIfAbsent(path) { return map.computeIfAbsent(path) {
try { try {

View File

@ -0,0 +1,400 @@
package ru.dbotthepony.kstarbound.io
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonElement
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.isSupertypeOf
private class PassthroughAdapter<T : Any?>(private val bound: Class<T>) : TypeAdapter<T?>() {
override fun write(out: JsonWriter, value: T?) {
Starbound.gson.toJson(Starbound.gson.toJsonTree(value, bound) as JsonElement, out)
}
override fun read(reader: JsonReader): T? {
return Starbound.gson.fromJson(reader, bound)
}
}
private fun <T> resolveBound(bound: Class<T>): TypeAdapter<T>? {
return when (bound) {
Float::class.java -> TypeAdapters.FLOAT as TypeAdapter<T>
Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter<T>
String::class.java -> TypeAdapters.STRING as TypeAdapter<T>
Int::class.java -> TypeAdapters.INTEGER as TypeAdapter<T>
Long::class.java -> TypeAdapters.LONG as TypeAdapter<T>
Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter<T>
else -> Starbound.gson.getAdapter(bound)
}
}
class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
private val resolvedBound by lazy {
resolveBound(bound)
}
override fun write(out: JsonWriter, value: List<T>) {
out.beginArray()
val resolvedBound = resolvedBound
if (resolvedBound != null) {
for (v in value) {
resolvedBound.write(out, v)
}
} else {
for (v in value) {
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
}
}
out.endArray()
}
override fun read(reader: JsonReader): List<T> {
reader.beginArray()
val builder = ImmutableList.builder<T>()
val resolvedBound = resolvedBound
if (resolvedBound != null) {
while (reader.peek() != JsonToken.END_ARRAY) {
val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
builder.add(readObject as T)
}
} else {
while (reader.peek() != JsonToken.END_ARRAY) {
val readObject = Starbound.gson.fromJson<T>(reader, bound) ?: throw JsonSyntaxException("List does not accept nulls")
builder.add(readObject)
}
}
reader.endArray()
return builder.build()
}
}
class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: Class<V>) : TypeAdapter<Map<K, V>>() {
private val resolvedKey by lazy {
resolveBound(boundKey)
}
private val resolvedValue by lazy {
resolveBound(boundValue)
}
override fun write(out: JsonWriter, value: Map<K, V>) {
out.beginArray()
val resolvedKey = resolvedKey
val resolvedValue = resolvedValue
if (resolvedKey != null && resolvedValue != null) {
for ((k, v) in value) {
out.beginArray()
resolvedKey.write(out, k)
resolvedValue.write(out, v)
out.endArray()
}
} else if (resolvedKey != null) {
for ((k, v) in value) {
out.beginArray()
resolvedKey.write(out, k)
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
out.endArray()
}
} else if (resolvedValue != null) {
for ((k, v) in value) {
out.beginArray()
Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
resolvedValue.write(out, v)
out.endArray()
}
} else {
for ((k, v) in value) {
out.beginArray()
Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
out.endArray()
}
}
out.endArray()
}
override fun read(reader: JsonReader): Map<K, V> {
reader.beginArray()
val builder = ImmutableMap.builder<K, V>()
val resolvedKey = resolvedKey
val resolvedValue = resolvedValue
if (resolvedKey != null && resolvedValue != null) {
while (reader.peek() != JsonToken.END_ARRAY) {
reader.beginArray()
builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
reader.endArray()
}
} else if (resolvedKey != null) {
while (reader.peek() != JsonToken.END_ARRAY) {
reader.beginArray()
builder.put(resolvedKey.read(reader), Starbound.gson.fromJson(reader, boundValue))
reader.endArray()
}
} else if (resolvedValue != null) {
while (reader.peek() != JsonToken.END_ARRAY) {
reader.beginArray()
builder.put(Starbound.gson.fromJson(reader, boundKey), resolvedValue.read(reader))
reader.endArray()
}
} else {
while (reader.peek() != JsonToken.END_ARRAY) {
reader.beginArray()
builder.put(Starbound.gson.fromJson(reader, boundKey), Starbound.gson.fromJson(reader, boundValue))
reader.endArray()
}
}
reader.endArray()
return builder.build()
}
}
class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String, V>>() {
private val resolvedBound by lazy {
resolveBound(bound)
}
override fun write(out: JsonWriter, value: Map<String, V>) {
val resolvedBound = resolvedBound
out.beginObject()
if (resolvedBound != null) {
for ((k, v) in value) {
out.name(k)
resolvedBound.write(out, v)
}
} else {
for ((k, v) in value) {
out.name(k)
Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
}
}
out.endObject()
}
override fun read(reader: JsonReader): Map<String, V> {
val builder = ImmutableMap.builder<String, V>()
reader.beginObject()
val resolvedBound = resolvedBound
if (resolvedBound != null) {
while (reader.peek() != JsonToken.END_OBJECT) {
builder.put(reader.nextName(), resolvedBound.read(reader))
}
} else {
while (reader.peek() != JsonToken.END_OBJECT) {
builder.put(reader.nextName(), Starbound.gson.fromJson(reader, bound))
}
}
reader.endObject()
return builder.build()
}
}
/**
* TypeAdapter для классов которые создаются единожды и более не меняются ("бетонных классов").
*/
class KConcreteTypeAdapter<T : Any>(
val bound: KClass<T>,
val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>
) : TypeAdapter<T>() {
private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
private val mapped = Object2IntArrayMap<String>()
private val loggedMisses = ObjectArraySet<String>()
init {
for ((field) in types) {
returnTypeCache[field] = field.returnType
}
mapped.defaultReturnValue(-1)
for ((i, pair) in types.withIndex()) {
mapped[pair.first.name] = i
}
}
private val factory: KFunction<T> = bound.constructors.firstOrNull first@{
if (it.parameters.size == types.size) {
val iterator = types.iterator()
for (param in it.parameters) {
val nextParam = iterator.next()
val a = param.type
val b = nextParam.first.returnType
if (!a.isSupertypeOf(b) || a.isMarkedNullable != b.isMarkedNullable) {
return@first false
}
}
return@first true
}
return@first false
} ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})")
override fun write(out: JsonWriter, value: T) {
out.beginObject()
for ((field, adapter) in types) {
out.name(field.name)
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
}
out.endObject()
}
override fun read(reader: JsonReader): T {
reader.beginObject()
val readValues = arrayOfNulls<Any>(types.size)
while (reader.peek() != JsonToken.END_OBJECT) {
val name = reader.nextName()
val fieldId = mapped.getInt(name)
if (fieldId == -1) {
if (loggedMisses.add(name)) {
LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
}
reader.skipValue()
} else {
val (field, adapter) = types[fieldId]
try {
readValues[fieldId] = adapter.read(reader)
} catch(err: Throwable) {
throw JsonSyntaxException("Exception reading field ${field.name}", err)
}
}
}
for ((i, pair) in types.withIndex()) {
val (field) = pair
if (readValues[i] == null && !returnTypeCache[field]!!.isMarkedNullable) {
throw JsonSyntaxException("Field ${field.name} does not accept nulls")
}
}
reader.endObject()
return factory.call(*readValues as Array<out Any>)
}
class Builder<T : Any>(val clazz: KClass<T>) {
private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>()
/**
* Добавляет поле без generic типов
*/
fun plain(field: KProperty1<T, *>): Builder<T> {
val returnType = field.returnType
val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!")
if (classifier.isSuperclassOf(Float::class)) {
types.add(field to TypeAdapters.FLOAT)
} else if (classifier.isSuperclassOf(Double::class)) {
types.add(field to TypeAdapters.DOUBLE)
} else if (classifier.isSuperclassOf(Int::class)) {
types.add(field to TypeAdapters.INTEGER)
} else if (classifier.isSuperclassOf(Long::class)) {
types.add(field to TypeAdapters.LONG)
} else if (classifier.isSuperclassOf(String::class)) {
types.add(field to TypeAdapters.STRING)
} else if (classifier.isSuperclassOf(Boolean::class)) {
types.add(field to TypeAdapters.BOOLEAN)
} else {
types.add(field to PassthroughAdapter(classifier.java))
}
return this
}
/**
* Добавляет поле, которое содержит список значений V (без null).
*
* Список неизменяем (создаётся объект [ImmutableList])
*/
fun <V> list(field: KProperty1<T, List<V>>, type: Class<V>): Builder<T> {
types.add(field to ListAdapter(type))
return this
}
/**
* Добавляет поле, которое содержит список значений V (без null).
*
* Список неизменяем (создаётся объект [ImmutableList])
*/
fun <V : Any> list(field: KProperty1<T, List<V>>, type: KClass<V>): Builder<T> {
return this.list(field, type.java)
}
/**
* Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...]
*
* Таблица неизменяема (создаётся объект [ImmutableMap])
*/
fun <K, V> map(field: KProperty1<T, Map<K, V>>, keyType: Class<K>, valueType: Class<V>): Builder<T> {
types.add(field to MapAdapter(keyType, valueType))
return this
}
/**
* Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
*
* Таблица неизменяема (создаётся объект [ImmutableMap])
*/
fun <V> map(field: KProperty1<T, Map<String, V>>, valueType: Class<V>): Builder<T> {
types.add(field to StringMapAdapter(valueType))
return this
}
fun build(): KConcreteTypeAdapter<T> {
return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types))
}
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.io package ru.dbotthepony.kstarbound.io
import com.google.common.collect.ImmutableList
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters import com.google.gson.internal.bind.TypeAdapters
@ -17,7 +18,14 @@ import kotlin.reflect.KType
import kotlin.reflect.full.isSuperclassOf import kotlin.reflect.full.isSuperclassOf
/** /**
* Kotlin property aware adapter * Kotlin property aware adapter.
*
* Создаёт пустые классы, а после наполняет их данными, что подходит для builder'ов с очень
* большим количеством возможных данных внутри.
*
*
*
* Подходит для игровых структур которые могут быть "разобраны" и пересобраны.
*/ */
class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapter<T>() { class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapter<T>() {
private val mappedFields = Object2ObjectArrayMap<String, KMutableProperty1<T, in Any?>>() private val mappedFields = Object2ObjectArrayMap<String, KMutableProperty1<T, in Any?>>()
@ -35,9 +43,14 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
} }
} }
val fields: Array<KMutableProperty1<T, in Any?>> get() { val fields: List<KMutableProperty1<T, in Any?>> by lazy {
val iterator = mappedFields.values.iterator() return@lazy ImmutableList.builder<KMutableProperty1<T, in Any?>>().let {
return Array(mappedFields.size) { iterator.next() } for (v in mappedFields.values.iterator()) {
it.add(v)
}
it.build()
}
} }
fun ignoreProperty(vararg value: String): KTypeAdapter<T> { fun ignoreProperty(vararg value: String): KTypeAdapter<T> {
@ -81,7 +94,7 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
val expectedType = mappedFieldsReturnTypes[name]!! val expectedType = mappedFieldsReturnTypes[name]!!
if (!expectedType.isMarkedNullable && peek == JsonToken.NULL) { if (!expectedType.isMarkedNullable && peek == JsonToken.NULL) {
throw IllegalArgumentException("Property ${field.name} of ${instance::class.qualifiedName} does not accept nulls") throw NullPointerException("Property ${field.name} of ${instance::class.qualifiedName} does not accept nulls")
} else if (peek == JsonToken.NULL) { } else if (peek == JsonToken.NULL) {
field.set(instance, null) field.set(instance, null)
reader.nextNull() reader.nextNull()
@ -108,8 +121,7 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
val read = reader.nextBoolean() val read = reader.nextBoolean()
field.set(instance, read) field.set(instance, read)
} else { } else {
val readElement = TypeAdapters.JSON_ELEMENT.read(reader) field.set(instance, Starbound.gson.fromJson(reader, classifier.java))
field.set(instance, Starbound.gson.fromJson(readElement, classifier.java))
} }
} else { } else {
throw IllegalStateException("Expected ${field.name} classifier to be KClass, got $classifier") throw IllegalStateException("Expected ${field.name} classifier to be KClass, got $classifier")
@ -140,4 +152,4 @@ class KTypeAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T,
companion object { companion object {
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java) private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
} }
} }