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

82 lines
2.7 KiB
Kotlin

package ru.dbotthepony.kstarbound.defs
import com.google.gson.Gson
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.ObjectOpenHashSet
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.io.Reader
import java.lang.reflect.ParameterizedType
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* Данный [AssetReferenceFactory] реализует возможности чтения данных по "ссылке" на файл.
*
* Созданный [TypeAdapter] имеет встроенный кеш.
*/
class AssetReferenceFactory(val remapper: AssetPathStack, val reader: (String) -> Reader?) : TypeAdapterFactory {
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, Any>()
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<Any>
private val strings = gson.getAdapter(String::class.java)
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
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 = remapper.remap(path)
val get = cache[fullPath]
if (get != null)
return AssetReference(path, fullPath, get)
if (fullPath in missing)
return null
val reader = reader.invoke(fullPath)
if (reader == null) {
missing.add(fullPath)
return AssetReference(path, fullPath, null)
}
val value = remapper(fullPath) {
adapter.read(JsonReader(reader).also {
it.isLenient = true
}) ?: return AssetReference(path, fullPath, null)
}
cache[fullPath] = value
return AssetReference(path, fullPath, value)
} else {
val value = adapter.read(`in`) ?: return null
return AssetReference(null, null, value)
}
}
} as TypeAdapter<T>
}
return null
}
}
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?)