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 create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == AssetReference::class.java) { val param = type.type as? ParameterizedType ?: return null return object : TypeAdapter>() { private val cache = ConcurrentHashMap() private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter private val strings = gson.getAdapter(String::class.java) private val missing = Collections.synchronizedSet(ObjectOpenHashSet()) override fun write(out: JsonWriter, value: AssetReference?) { if (value == null) out.nullValue() else out.value(value.fullPath) } override fun read(`in`: JsonReader): AssetReference? { 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 } return null } } data class AssetReference(val path: String?, val fullPath: String?, val value: V?)