aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/FeatureTogglesByDefaultAdapter.kt
blob: f78a0c4dc92470c60cf4157884ae21f8176b4a5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package at.hannibal2.skyhanni.utils

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)
    }
}