aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/test/SkyHanniConfigSearchResetCommand.kt
blob: aa76d570615bc98f992d16529b628584286ff946 (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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package at.hannibal2.skyhanni.test

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.config.Features
import at.hannibal2.skyhanni.config.core.config.Position
import at.hannibal2.skyhanni.test.command.CopyErrorCommand
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
import at.hannibal2.skyhanni.utils.OSUtils
import java.lang.reflect.Field

object SkyHanniConfigSearchResetCommand {
    fun command(args: Array<String>) {
        if (args.isEmpty()) {
            LorenzUtils.chat("§c[SkyHanni] This is a config-edit command, only use it if you know what you are doing!")
            return
        }
        if (args[0] == "reset") {
            if (args.size != 2) {
                LorenzUtils.chat("§c/shconfig reset <config element>")
                return
            }
            val term = args[1]
            try {
                val (field, defaultObject, _) = getComplexField(term, Features())
                val (_, _, parent) = getComplexField(term, SkyHanniMod.feature)
                field.set(parent, defaultObject)
                LorenzUtils.chat("§eSuccessfully reset config element '$term'")
            } catch (e: Exception) {
                CopyErrorCommand.logError(e, "Could not reset config element '$term'")
            }
            return
        } else if (args[0] == "search") {
            if (args.size == 1) {
                LorenzUtils.chat("§c/shconfig search <config name> [class name]")
                return
            }
            Thread {
                try {
                    startSearch(args)
                } catch (e: Exception) {
                    CopyErrorCommand.logError(e, "Error while trying to search config")
                }
            }.start()
            return
        }

        LorenzUtils.chat("§c/shconfig <search;reset>")
    }

    private fun createFilter(condition: Boolean, searchTerm: () -> String): Pair<(String) -> Boolean, String> {
        return if (condition && searchTerm() != "all") {
            val term = searchTerm()
            Pair({ it.lowercase().contains(term) }, "'$term'")
        } else Pair({ true }, "<all>")
    }

    private fun startSearch(args: Array<String>) {
        val (configFilter, configSearchTerm) = createFilter(true) { args[1].lowercase() }
        val (classFilter, classSearchTerm) = createFilter(args.size == 3) { args[2].lowercase() }

        val elements = findConfigElements(configFilter, classFilter)
        val builder = StringBuilder()
        builder.append("```\n")
        builder.append("Search config for SkyHanni ${SkyHanniMod.version}\n")
        builder.append("configSearchTerm: $configSearchTerm\n")
        builder.append("classSearchTerm: $classSearchTerm\n")
        builder.append("\n")
        val size = elements.size
        builder.append("Found $size config elements:\n")
        for (entry in elements) {
            builder.append(entry)
            builder.append("\n")
        }
        builder.append("```")
        OSUtils.copyToClipboard(builder.toString())
        LorenzUtils.chat("§eCopied search result ($size) to clipboard.")
    }

    private fun findConfigElements(
        configFilter: (String) -> Boolean,
        classFilter: (String) -> Boolean,
    ): MutableList<String> {
        val list = mutableListOf<String>()
        for ((name, obj) in loadAllFields("config", Features())) {
            if (name == "config.DISCORD") continue
            if (name == "config.GITHUB") continue
            val description = if (obj != null) {
                val className = obj.getClassName()
                if (!classFilter(className)) continue
                val objectName = obj.getObjectName()
                if (objectName.startsWith(className) && objectName.startsWith("at.hannibal2.skyhanni.config.features.")) {
                    "<category>"
                } else {
                    "$className = $objectName"
                }
            } else "null"

            if (configFilter(name)) {
                list.add("$name $description")
            }
        }
        return list
    }

    private fun getComplexField(term: String, startObject: Any): Triple<Field, Any, Any> {
        var parentObject = startObject
        var obj = startObject
        val line = term.split(".").drop(1)
        var field: Field? = null
        for (entry in line) {
            field = obj.javaClass.getField(entry)
            field.isAccessible = true
            parentObject = obj
            obj = field.get(obj)
        }
        if (field == null) {
            throw Error("Could not find field for '$term'")
        }
        return Triple(field, obj, parentObject)
    }

    private fun loadAllFields(parentName: String, obj: Any, depth: Int = 0): Map<String, Any?> {
        if (depth == 10) return emptyMap() // this is only a safety backup, needs increasing maybe someday
        val map = mutableMapOf<String, Any?>()
        for (field in obj.javaClass.fields) {
            val name = field.name
            val fieldName = "$parentName.$name"
            field.isAccessible = true
            val newObj = field.get(obj)
            map[fieldName] = newObj
            if (newObj != null) {
                if (newObj !is Boolean && newObj !is String && newObj !is Long && newObj !is Int) {
                    if (newObj !is Position) {
                        map.putAll(loadAllFields(fieldName, newObj, depth + 1))
                    }
                }
            }
        }

        return map
    }

    private fun Any.getClassName(): String {
        // we do not use javaClass.simpleName since we want to catch edge cases
        val name = javaClass.name
        return when (name) {
            "at.hannibal2.skyhanni.config.core.config.Position" -> "Position"
            "java.lang.Boolean" -> "Boolean"
            "java.lang.Integer" -> "Int"
            "java.lang.Long" -> "Long"
            "java.lang.String" -> "String"
            "io.github.moulberry.moulconfig.observer.Property" -> "moulconfig.Property"
            "java.util.ArrayList" -> "List"
            "java.util.HashMap" -> "Map"

            else -> name
        }
    }

    private fun Field.makeAccessible() = also { isAccessible = true }

    private fun Any.getObjectName(): String {
        if (this is Position) {
            val x = javaClass.getDeclaredField("x").makeAccessible().get(this)
            val y = javaClass.getDeclaredField("y").makeAccessible().get(this)
            return "($x, $y)"
        }
        if (this is String) {
            return if (toString() == "") {
                "<empty string>"
            } else {
                "'$this'"
            }
        }
        if (this is io.github.moulberry.moulconfig.observer.Property<*>) {
            val value = javaClass.getDeclaredField("value").makeAccessible().get(this)
            val name = value.getClassName()
            return "moulconfig.Property<$name> = ${value.getObjectName()}"
        }

        if (this is Int || this is Int) return addSeparators()

        return toString()
    }
}