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 > EnumAdapter(values: Stream = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter { return EnumAdapter(T::class, values, default) } inline fun > EnumAdapter(values: Iterator, default: T? = null): EnumAdapter { return EnumAdapter(T::class, Streams.stream(values), default) } inline fun > EnumAdapter(values: Array, default: T? = null): EnumAdapter { return EnumAdapter(T::class, Arrays.stream(values), default) } inline fun > EnumAdapter(values: Collection, default: T? = null): EnumAdapter { return EnumAdapter(T::class, values.stream(), default) } class EnumAdapter>(private val enum: KClass, values: Stream = Arrays.stream(enum.java.enumConstants), val default: T? = null) : TypeAdapter() { constructor(clazz: Class, values: Stream = Arrays.stream(clazz.enumConstants), default: T? = null) : this(clazz.kotlin, values, default) constructor(clazz: Class, values: Iterator, default: T? = null) : this(clazz.kotlin, Streams.stream(values), default) constructor(clazz: Class, values: Array, default: T? = null) : this(clazz.kotlin, Arrays.stream(values), default) constructor(clazz: Class, values: Collection, default: T? = null) : this(clazz.kotlin, values.stream(), default) constructor(clazz: KClass, values: Iterator, default: T? = null) : this(clazz, Streams.stream(values), default) constructor(clazz: KClass, values: Array, default: T? = null) : this(clazz, Arrays.stream(values), default) constructor(clazz: KClass, values: Collection, default: T? = null) : this(clazz, values.stream(), default) private val values = values.collect(ImmutableList.toImmutableList()) private val mapping: ImmutableMap private val areCustom = IStringSerializable::class.java.isAssignableFrom(enum.java) private val misses = Collections.synchronizedSet(ObjectOpenHashSet()) init { val builder = Object2ObjectArrayMap() 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 { return object : TypeAdapter() { 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 create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType.isEnum) { val clazz = type.rawType as Class return EnumAdapter(clazz, clazz.enumConstants) as TypeAdapter } return null } } }