package ru.dbotthepony.kstarbound import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.google.gson.internal.bind.JsonTreeReader import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectMaps import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.lua.LuaState import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.set import ru.dbotthepony.kstarbound.util.traverseJsonPath import java.util.* import kotlin.reflect.KClass inline fun ObjectRegistry(name: String, noinline key: ((T) -> String)? = null, noinline intKey: ((T) -> Int)? = null): ObjectRegistry { return ObjectRegistry(T::class, name, key, intKey) } fun mergeJsonElements(source: JsonObject, destination: JsonObject): JsonObject { for ((k, v) in source.entrySet()) { if (!destination.has(k)) { destination[k] = v.deepCopy() } else { mergeJsonElements(v, destination[k]) } } return destination } fun mergeJsonElements(source: JsonArray, destination: JsonArray): JsonArray { for ((i, v) in source.withIndex()) { if (i >= destination.size()) { destination.add(v.deepCopy()) } else { destination[i] = mergeJsonElements(v, destination[i]) } } return destination } fun mergeJsonElements(source: JsonElement, destination: JsonElement): JsonElement { if (destination is JsonPrimitive) { return destination } if (destination is JsonObject && source is JsonObject) { return mergeJsonElements(source, destination) } if (destination is JsonArray && source is JsonArray) { return mergeJsonElements(source, destination) } return destination } class RegistryObject( /** * Объект реестра */ val value: T, /** * Оригинальный JSON объекта без каких либо изменений */ val json: JsonElement, /** * Файл, откуда данный объект был загружен */ val file: IStarboundFile, ) { val jsonObject get() = json as JsonObject fun push(lua: LuaState) { lua.push(toJson()) } fun push(lua: LuaState.ArgStack) { lua.push(toJson()) } /** * Возвращает полную (обработанную) структуру [JsonObject] объекта [value] * * Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны * в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт. */ fun toJson(): JsonElement { return mergeJsonElements(json, Starbound.gson.toJsonTree(value)) } fun traverseJsonPath(path: String): JsonElement? { return traverseJsonPath(path, mergeJsonElements(json, Starbound.gson.toJsonTree(value))) } override fun equals(other: Any?): Boolean { return other === this || other is RegistryObject<*> && other.value == value && other.json == json } private var computedHash = false private var hash = 0 override fun hashCode(): Int { if (!computedHash) { hash = value.hashCode().rotateRight(13) xor json.hashCode() computedHash = true } return hash } override fun toString(): String { return "RegistryObject[$value from $file]" } } class ObjectRegistry(val clazz: KClass, val name: String, val key: ((T) -> String)? = null, val intKey: ((T) -> Int)? = null) { private val objects = Object2ObjectOpenHashMap>() private val intObjects = Int2ObjectOpenHashMap>() val view: Map> = Collections.unmodifiableMap(objects) val intView: Int2ObjectMap> = Int2ObjectMaps.unmodifiable(intObjects) fun clear() { objects.clear() intObjects.clear() } fun add(file: IStarboundFile): Boolean { return AssetPathStack(file.computeDirectory()) { val elem = Starbound.gson.fromJson(file.reader(), JsonElement::class.java) val value = Starbound.gson.fromJson(JsonTreeReader(elem), clazz.java) add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper")) } } fun add(value: T, json: JsonElement, file: IStarboundFile): Boolean { return add(RegistryObject(value, json, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper")) } fun add(value: T, json: JsonElement, file: IStarboundFile, key: String): Boolean { return add(RegistryObject(value, json, file), key) } private fun add(value: RegistryObject, key: String): Boolean { val existing = objects.put(key, value) if (existing != null) { LOGGER.warn("Registry $name already has object with key $key! Overwriting. (old originated from ${existing.file}, new originate from ${value.file}).") } if (this.intKey == null) return existing != null val intKey = this.intKey.invoke(value.value) val intExisting = intObjects.put(intKey, value) if (intExisting != null) { LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${ objects.entries.firstOrNull { it.value === intExisting }?.key })! Overwriting. (old originated from ${intExisting.file}, new originate from ${value.file}).") } return existing != null || intExisting != null } companion object { private val LOGGER = LogManager.getLogger() } }