164 lines
5.5 KiB
Kotlin
164 lines
5.5 KiB
Kotlin
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
|
||
}
|
||
}
|
||
}
|