KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt

96 lines
3.2 KiB
Kotlin

package ru.dbotthepony.kstarbound.defs
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.internal.bind.JsonTreeReader
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.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.lang.reflect.ParameterizedType
import java.util.*
import java.util.concurrent.ConcurrentHashMap
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?, val json: JsonElement?) {
companion object : TypeAdapterFactory {
val EMPTY = AssetReference(null, null, null, null)
fun <V> empty() = EMPTY as AssetReference<V>
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == AssetReference::class.java) {
val param = type.type as? ParameterizedType ?: return null
return object : TypeAdapter<AssetReference<Any>>() {
private val cache = ConcurrentHashMap<String, Pair<Any, JsonElement>>()
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<Any>
private val strings = gson.getAdapter(String::class.java)
private val jsons = gson.getAdapter(JsonElement::class.java)
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
private val logger = LogManager.getLogger()
override fun write(out: JsonWriter, value: AssetReference<Any>?) {
if (value == null)
out.nullValue()
else
out.value(value.fullPath)
}
override fun read(`in`: JsonReader): AssetReference<Any>? {
if (`in`.peek() == JsonToken.NULL) {
return null
} else if (`in`.peek() == JsonToken.STRING) {
val path = strings.read(`in`)!!
val fullPath = AssetPathStack.remap(path)
val get = cache[fullPath]
if (get != null)
return AssetReference(path, fullPath, get.first, get.second)
if (fullPath in missing)
return null
val file = Starbound.locate(fullPath)
if (!file.exists) {
logger.error("File does not exist: ${file.computeFullPath()}")
missing.add(fullPath)
return AssetReference(path, fullPath, null, null)
}
val reader = file.reader()
val json = jsons.read(JsonReader(reader).also {
it.isLenient = true
})
val value = AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
adapter.read(JsonTreeReader(json))
}
if (value == null) {
missing.add(fullPath)
return AssetReference(path, fullPath, null, json)
}
cache[fullPath] = value to json
return AssetReference(path, fullPath, value, json)
} else {
val json = jsons.read(`in`)!!
val value = adapter.read(JsonTreeReader(json)) ?: return null
return AssetReference(null, null, value, json)
}
}
} as TypeAdapter<T>
}
return null
}
}
}