KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/EnumAdapter.kt

164 lines
5.5 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.json.builder
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.Streams
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.util.sbIntern
import java.util.*
import java.util.stream.Stream
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
interface IStringSerializable {
val jsonName: String
fun match(name: String): Boolean {
// there are more inconsistencies than consistencies
// where original engine ignores casing and where it does not
return name.lowercase() == jsonName.lowercase()
}
}
inline fun <reified T : Enum<T>> EnumAdapter(values: Stream<T> = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter<T> {
return EnumAdapter(T::class, values, default)
}
inline fun <reified T : Enum<T>> EnumAdapter(values: Iterator<T>, default: T? = null): EnumAdapter<T> {
return EnumAdapter(T::class, Streams.stream(values), default)
}
inline fun <reified T : Enum<T>> EnumAdapter(values: Array<out T>, default: T? = null): EnumAdapter<T> {
return EnumAdapter(T::class, Arrays.stream(values), default)
}
inline fun <reified T : Enum<T>> EnumAdapter(values: Collection<T>, default: T? = null): EnumAdapter<T> {
return EnumAdapter(T::class, values.stream(), default)
}
class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> = Arrays.stream(enum.java.enumConstants), val default: T? = null) : TypeAdapter<T?>() {
constructor(clazz: Class<T>, values: Stream<T> = Arrays.stream(clazz.enumConstants), default: T? = null) : this(clazz.kotlin, values, default)
constructor(clazz: Class<T>, values: Iterator<T>, default: T? = null) : this(clazz.kotlin, Streams.stream(values), default)
constructor(clazz: Class<T>, values: Array<out T>, default: T? = null) : this(clazz.kotlin, Arrays.stream(values), default)
constructor(clazz: Class<T>, values: Collection<T>, default: T? = null) : this(clazz.kotlin, values.stream(), default)
constructor(clazz: KClass<T>, values: Iterator<T>, default: T? = null) : this(clazz, Streams.stream(values), default)
constructor(clazz: KClass<T>, values: Array<out T>, default: T? = null) : this(clazz, Arrays.stream(values), default)
constructor(clazz: KClass<T>, values: Collection<T>, default: T? = null) : this(clazz, values.stream(), default)
private val values = values.collect(ImmutableList.toImmutableList())
private val mapping: ImmutableMap<String, T>
private val areCustom = IStringSerializable::class.java.isAssignableFrom(enum.java)
private val misses = Collections.synchronizedSet(ObjectOpenHashSet<String>())
init {
val builder = Object2ObjectArrayMap<String, T>()
for (value in this.values) {
builder[value.name.sbIntern()] = value
builder[value.name.uppercase().sbIntern()] = value
builder[value.name.lowercase().sbIntern()] = value
val spaced = value.name.replace('_', ' ').sbIntern()
val stitched = value.name.replace("_", "").sbIntern()
builder[spaced.sbIntern()] = value
builder[spaced.uppercase().sbIntern()] = value
builder[spaced.lowercase().sbIntern()] = value
builder[stitched.sbIntern()] = value
builder[stitched.uppercase().sbIntern()] = value
builder[stitched.lowercase().sbIntern()] = value
}
mapping = ImmutableMap.copyOf(builder)
}
override fun write(out: JsonWriter, value: T?) {
if (value == null) {
out.nullValue()
} else if (value is IStringSerializable) {
out.value(value.jsonName)
} else {
out.value(value.name)
}
}
@Suppress("unchecked_cast")
override fun read(`in`: JsonReader): T? {
if (`in`.consumeNull()) {
return null
}
val key = `in`.nextString()
if (areCustom) {
for (value in values) {
if ((value as IStringSerializable).match(key)) {
return value
}
}
}
val existing = mapping[key]
if (existing == null && misses.add(key)) {
LOGGER.error("$key is not a valid ${enum.qualifiedName} value")
}
return existing ?: default
}
/**
* Возвращает [EnumAdapter] с тем же типом и набором [T], которому запрещено возвращать или принимать null'ы
*/
fun neverNull(): TypeAdapter<T> {
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
return this@EnumAdapter.write(out, value)
}
override fun read(`in`: JsonReader): T {
val key = `in`.nextString()
if (areCustom) {
for (value in values) {
if ((value as IStringSerializable).match(key)) {
return value
}
}
}
return mapping[key] ?: default ?: throw JsonSyntaxException("$key is not a valid ${enum.qualifiedName} value")
}
}
}
private enum class Never
companion object : TypeAdapterFactory {
private val LOGGER = LogManager.getLogger()
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType.isEnum) {
val clazz = type.rawType as Class<Never>
return EnumAdapter(clazz, clazz.enumConstants) as TypeAdapter<T>
}
return null
}
}
}