diff options
| author | CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> | 2024-06-04 04:16:22 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-03 20:16:22 +0200 |
| commit | aa66334dba2c2c086b94eab1767abd4765e006f7 (patch) | |
| tree | 42c9cc1d76f1d331a42c0bee2e55830168e6f85a /src/main/java/at/hannibal2/skyhanni/utils/json | |
| parent | fde5544ca7a49f8315318a249607769aee87661c (diff) | |
| download | skyhanni-aa66334dba2c2c086b94eab1767abd4765e006f7.tar.gz skyhanni-aa66334dba2c2c086b94eab1767abd4765e006f7.tar.bz2 skyhanni-aa66334dba2c2c086b94eab1767abd4765e006f7.zip | |
Fix: Config resetting on wrong value (#1979)
Co-authored-by: Brady <brady@thatgravyboat.tech>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils/json')
8 files changed, 356 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt new file mode 100644 index 000000000..d73edc0b0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/BaseGsonBuilder.kt @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.utils.json + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.features.fishing.trophy.TrophyRarity +import at.hannibal2.skyhanni.utils.KotlinTypeAdapterFactory +import at.hannibal2.skyhanni.utils.LorenzRarity +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker +import com.google.gson.GsonBuilder +import io.github.notenoughupdates.moulconfig.observer.PropertyTypeAdapterFactory +import net.minecraft.item.ItemStack +import java.util.UUID + +object BaseGsonBuilder { + fun gson(): GsonBuilder = GsonBuilder().setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .serializeSpecialFloatingPointValues() + .registerTypeAdapterFactory(PropertyTypeAdapterFactory()) + .registerTypeAdapterFactory(KotlinTypeAdapterFactory()) + .registerTypeAdapter(UUID::class.java, SkyHanniTypeAdapters.UUID.nullSafe()) + .registerTypeAdapter(LorenzVec::class.java, SkyHanniTypeAdapters.VEC_STRING.nullSafe()) + .registerTypeAdapter(TrophyRarity::class.java, SkyHanniTypeAdapters.TROPHY_RARITY.nullSafe()) + .registerTypeAdapter(ItemStack::class.java, SkyHanniTypeAdapters.NEU_ITEMSTACK.nullSafe()) + .registerTypeAdapter(NEUInternalName::class.java, SkyHanniTypeAdapters.INTERNAL_NAME.nullSafe()) + .registerTypeAdapter(LorenzRarity::class.java, SkyHanniTypeAdapters.RARITY.nullSafe()) + .registerTypeAdapter(IslandType::class.java, SkyHanniTypeAdapters.ISLAND_TYPE.nullSafe()) + .registerTypeAdapter( + SkyHanniTracker.DefaultDisplayMode::class.java, + SkyHanniTypeAdapters.TRACKER_DISPLAY_MODE.nullSafe() + ) + .registerTypeAdapter(SimpleTimeMark::class.java, SkyHanniTypeAdapters.TIME_MARK.nullSafe()) + .enableComplexMapKeySerialization() + + fun lenientGson(): GsonBuilder = gson().registerTypeAdapterFactory(SkippingTypeAdapterFactory) +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/FeatureTogglesByDefaultAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/FeatureTogglesByDefaultAdapter.kt new file mode 100644 index 000000000..bcc9c2edf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/FeatureTogglesByDefaultAdapter.kt @@ -0,0 +1,89 @@ +package at.hannibal2.skyhanni.utils.json + +import at.hannibal2.skyhanni.config.FeatureToggle +import at.hannibal2.skyhanni.utils.ReflectionUtils.getDeclaredFieldOrNull +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +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 java.lang.reflect.Field +import java.lang.reflect.Type +import com.google.gson.internal.`$Gson$Types` as InternalGsonTypes + +object FeatureTogglesByDefaultAdapter : TypeAdapterFactory { + fun <T> getType(typeToken: TypeToken<T>, field: Field): Type { + return InternalGsonTypes.resolve(typeToken.type, typeToken.rawType, field.genericType) + } + + class Adapter<T>( + val originalWrite: TypeAdapter<T>, + val clazz: Class<T>, + val gson: Gson, + val type: TypeToken<T>, + ) : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T) { + // Delegate the original config write, since that one is unchanged + originalWrite.write(out, value) + } + + override fun read(reader: JsonReader): T { + reader.beginObject() + // Create a default initialized instance + val obj = clazz.newInstance() + + // Overwrite the default with true (or false) for feature toggles + clazz.fields.forEach { + val featureToggle = it.getAnnotation(FeatureToggle::class.java) + val adapt = gson.getAdapter(TypeToken.get(getType(type, it))) + if (featureToggle != null) + it.set(obj, adapt.read(JsonTreeReader(JsonPrimitive(featureToggle.trueIsEnabled)))) + if (adapt is Adapter) { + it.set(obj, adapt.read(JsonTreeReader(JsonObject()))) + } + } + + // Read the actual JSON Object + while (reader.peek() != JsonToken.END_OBJECT) { + // IllegalStateException: Expected NAME but was BOOLEAN + if (reader.peek() != JsonToken.NAME) { + reader.skipValue() + continue + } + val name = reader.nextName() + val field = clazz.getDeclaredFieldOrNull(name) + if (field == null) { + println("field is in config file, but not in object file: $name") + continue + } + val fieldType = gson.getAdapter(TypeToken.get(getType(type, field))) + // Read the field data + val data = fieldType.read(reader) + // Set the field or override the feature toggle with the saved data, leaving only the unset feature toggles to deviate from their defaults + field.set(obj, data) + } + + reader.endObject() + return obj + } + } + + override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>): TypeAdapter<T>? { + gson!! + val t = type.rawType + + // Check if this object has any feature toggles present + if (t.fields.none { + it.isAnnotationPresent(FeatureToggle::class.java) || + gson.getAdapter(TypeToken.get(getType(type, it))) is Adapter + }) return null + + val originalWrite = gson.getDelegateAdapter(this, type) + return Adapter(originalWrite, t as Class<T>, gson, type) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/ItemStackTypeAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/ItemStackTypeAdapter.kt new file mode 100644 index 000000000..1940b03c0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/ItemStackTypeAdapter.kt @@ -0,0 +1,29 @@ +package at.hannibal2.skyhanni.utils.json + +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.JsonWriter +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound + +object ItemStackTypeAdapterFactory : TypeAdapterFactory { + + override fun <T : Any> create(gson: Gson?, type: TypeToken<T>): TypeAdapter<T>? { + if (type.rawType == ItemStack::class.java) { + val nbtCompoundTypeAdapter = gson!!.getAdapter(NBTTagCompound::class.java) + return object : TypeAdapter<ItemStack>() { + override fun write(out: JsonWriter, value: ItemStack) { + nbtCompoundTypeAdapter.write(out, value.serializeNBT()) + } + + override fun read(reader: JsonReader): ItemStack { + return ItemStack.loadItemStackFromNBT(nbtCompoundTypeAdapter.read(reader)) + } + } as TypeAdapter<T> + } + return null + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt new file mode 100644 index 000000000..750bd96aa --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/JsonUtils.kt @@ -0,0 +1,37 @@ +package at.hannibal2.skyhanni.utils.json + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import java.io.Reader +import kotlin.reflect.jvm.javaType +import kotlin.reflect.typeOf + +inline fun <reified T : Any> Gson.fromJson(string: String): T = this.fromJson(string, typeOf<T>().javaType) + +inline fun <reified T : Any> Gson.fromJson(jsonElement: JsonElement): T = + this.fromJson(jsonElement, typeOf<T>().javaType) + +inline fun <reified T : Any> Gson.fromJson(reader: Reader): T = this.fromJson(reader, typeOf<T>().javaType) + +/** + * Straight forward deep copy. This is included in gson as well, but different versions have it exposed privately instead of publicly, + * so this reimplementation is here as an always public alternative. + */ +fun JsonElement.shDeepCopy(): JsonElement { + return when (this) { + is JsonObject -> JsonObject().also { + for (entry in this.entrySet()) + it.add(entry.key, entry.value.shDeepCopy()) + } + + is JsonArray -> JsonArray().also { + for (entry in this) { + it.add(entry.shDeepCopy()) + } + } + + else -> this + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/NBTTypeAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/NBTTypeAdapter.kt new file mode 100644 index 000000000..e2e782636 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/NBTTypeAdapter.kt @@ -0,0 +1,24 @@ +package at.hannibal2.skyhanni.utils.json + +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import net.minecraft.nbt.CompressedStreamTools +import net.minecraft.nbt.NBTTagCompound +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.Base64 + +object NBTTypeAdapter : TypeAdapter<NBTTagCompound>() { + + override fun write(out: JsonWriter, value: NBTTagCompound) { + val baos = ByteArrayOutputStream() + CompressedStreamTools.writeCompressed(value, baos) + out.value(Base64.getEncoder().encode(baos.toByteArray()).decodeToString()) + } + + override fun read(reader: JsonReader): NBTTagCompound { + val bais = ByteArrayInputStream(Base64.getDecoder().decode(reader.nextString())) + return CompressedStreamTools.readCompressed(bais) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/SimpleStringTypeAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/SimpleStringTypeAdapter.kt new file mode 100644 index 000000000..21f601932 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/SimpleStringTypeAdapter.kt @@ -0,0 +1,29 @@ +package at.hannibal2.skyhanni.utils.json + +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter + +class SimpleStringTypeAdapter<T>( + val serializer: T.() -> String, + val deserializer: String.() -> T +) : TypeAdapter<T>() { + + override fun write(writer: JsonWriter, value: T) { + writer.value(serializer(value)) + } + + override fun read(reader: JsonReader): T { + return deserializer(reader.nextString()) + } + + companion object { + + inline fun <reified T : Enum<T>> forEnum(): SimpleStringTypeAdapter<T> { + return SimpleStringTypeAdapter( + { name }, + { enumValueOf(this.replace(" ", "_").uppercase()) } + ) + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/SkippingTypeAdapterFactory.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/SkippingTypeAdapterFactory.kt new file mode 100644 index 000000000..a926976ca --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/SkippingTypeAdapterFactory.kt @@ -0,0 +1,32 @@ +package at.hannibal2.skyhanni.utils.json + +import at.hannibal2.skyhanni.SkyHanniMod +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.JsonWriter + +object SkippingTypeAdapterFactory : TypeAdapterFactory { + + override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> { + return SafeTypeAdapter(gson.getDelegateAdapter(this, type)) + } + + private class SafeTypeAdapter<T>(val parent: TypeAdapter<T>) : TypeAdapter<T>() { + override fun write(writer: JsonWriter, value: T) { + parent.write(writer, value) + } + + override fun read(reader: JsonReader): T? { + return try { + parent.read(reader) + } catch (e: Exception) { + SkyHanniMod.logger.warn("Failed to read value from JSON, skipping", e) + reader.skipValue() + null + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt new file mode 100644 index 000000000..dc50eef74 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/json/SkyHanniTypeAdapters.kt @@ -0,0 +1,79 @@ +package at.hannibal2.skyhanni.utils.json + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.features.fishing.trophy.TrophyRarity +import at.hannibal2.skyhanni.features.garden.CropType +import at.hannibal2.skyhanni.features.garden.pests.PestType +import at.hannibal2.skyhanni.utils.LorenzRarity +import at.hannibal2.skyhanni.utils.LorenzVec +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NEUItems +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.asTimeMark +import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker +import com.google.gson.GsonBuilder +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import net.minecraft.item.ItemStack +import java.util.UUID + +object SkyHanniTypeAdapters { + val NEU_ITEMSTACK: TypeAdapter<ItemStack> = SimpleStringTypeAdapter(NEUItems::saveNBTData, NEUItems::loadNBTData) + + val UUID: TypeAdapter<UUID> = SimpleStringTypeAdapter( + { this.toString() }, + { java.util.UUID.fromString(this) } + ) + + val INTERNAL_NAME: TypeAdapter<NEUInternalName> = SimpleStringTypeAdapter( + { this.asString() }, + { this.asInternalName() } + ) + + val VEC_STRING: TypeAdapter<LorenzVec> = SimpleStringTypeAdapter( + { "$x:$y:$z" }, + { LorenzVec.decodeFromString(this) } + ) + + val TROPHY_RARITY: TypeAdapter<TrophyRarity> = SimpleStringTypeAdapter( + { name }, + { TrophyRarity.getByName(this) ?: error("Could not parse TrophyRarity from '$this'") } + ) + + val TIME_MARK: TypeAdapter<SimpleTimeMark> = object : TypeAdapter<SimpleTimeMark>() { + override fun write(out: JsonWriter, value: SimpleTimeMark) { + out.value(value.toMillis()) + } + + override fun read(reader: JsonReader): SimpleTimeMark { + return reader.nextString().toLong().asTimeMark() + } + } + + val CROP_TYPE: TypeAdapter<CropType> = SimpleStringTypeAdapter( + { name }, + { CropType.getByName(this) } + ) + + val PEST_TYPE: TypeAdapter<PestType> = SimpleStringTypeAdapter( + { name }, + { PestType.getByName(this) } + ) + + val TRACKER_DISPLAY_MODE = SimpleStringTypeAdapter.forEnum<SkyHanniTracker.DefaultDisplayMode>() + val ISLAND_TYPE = SimpleStringTypeAdapter.forEnum<IslandType>() + val RARITY = SimpleStringTypeAdapter.forEnum<LorenzRarity>() + + inline fun <reified T> GsonBuilder.registerTypeAdapter( + crossinline write: (JsonWriter, T) -> Unit, + crossinline read: (JsonReader) -> T, + ): GsonBuilder { + this.registerTypeAdapter(T::class.java, object : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T) = write(out, value) + override fun read(reader: JsonReader) = read(reader) + }.nullSafe()) + return this + } +} |
