summaryrefslogtreecommitdiff
path: root/src/main/kotlin/com/walkerselby/wheelchair/client/Formatter.kt
blob: c48d34c83423c94ab74eeff40c29e8fe730ab9a9 (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
package com.walkerselby.wheelchair.client

import it.unimi.dsi.fastutil.ints.Int2ObjectMap
import net.minecraft.item.ItemStack
import net.minecraft.network.packet.Packet
import net.minecraft.text.Text
import java.lang.reflect.Field
import java.lang.reflect.Modifier

val Field.isStatic: Boolean
    get() = (modifiers and Modifier.STATIC) != 0

interface Printer {
    fun emitName(value: String)
    fun emitValue(value: String)
    fun beginObject(label: String): Printer
    fun beginList(label: String): Printer
}

class AccumulatingPrinter(
    val indent: Int,
    val stringBuilder: StringBuilder,
    val isListBuilder: Boolean,
) : Printer {

    fun appendIndent() {
        stringBuilder.append(" ".repeat(indent))
    }

    override fun emitName(value: String) {
        if (isListBuilder) {
            error("Cannot print name within a list")
        }
        appendIndent()
        stringBuilder.append(value)
        stringBuilder.append(": ")
    }

    override fun emitValue(value: String) {
        if (isListBuilder)
            stringBuilder.append("* ")
        else
            stringBuilder.append("")
        stringBuilder.append(value)
        stringBuilder.append("\n")
    }

    override fun beginObject(label: String): Printer {
        if (isListBuilder) {
            stringBuilder.append("* $label:\n")
        } else {
            stringBuilder.append("$label\n")
        }
        return AccumulatingPrinter(indent + 2, stringBuilder, false)
    }

    override fun beginList(label: String): Printer {
        if (isListBuilder) {
            stringBuilder.append("* $label\n")
        } else {
            stringBuilder.append("$label:\n")
        }
        return AccumulatingPrinter(indent + 2, stringBuilder, true)
    }

}

fun interface Formatter<T> {

    fun prettyPrint(subject: T, printer: Printer)

    companion object {
        private val formatters = mutableMapOf<Class<*>, Formatter<*>>()
        private val dynamicFormatter = mutableListOf<(Class<*>) -> Formatter<*>?>()
        private val processing = mutableSetOf<Class<*>>()

        fun <V> putFormatter(clazz: Class<V>, formatter: Formatter<V>) {
            formatters[clazz] = formatter
        }

        fun <V> putFormatter(function: (Class<in V>) -> Formatter<V>?) {
            dynamicFormatter.add(function as ((Class<*>) -> Formatter<*>))
        }

        init {
            putFormatter(
                java.lang.Integer.TYPE
            ) { subject, printer -> printer.emitValue(subject.toString()) }
            putFormatter(
                java.lang.Integer::class.java
            ) { subject, printer -> printer.emitValue(subject.toString()) }
            putFormatter(
                String::class.java
            ) { subject, printer -> printer.emitValue(subject) }
            putFormatter(
                ItemStack::class.java
            ) { subject, printer -> printer.emitValue("${subject.count}x${subject.name.string}§r") }
            putFormatter {
                if (Int2ObjectMap::class.java.isAssignableFrom(it)) {
                    return@putFormatter object : Formatter<Int2ObjectMap<*>> {
                        override fun prettyPrint(subject: Int2ObjectMap<*>, printer: Printer) {
                            val child = printer.beginObject("Int2ObjectMap (length=${subject.size})")
                            subject.forEach { (k, v) ->
                                val valueFormatter = getPrettyPrinter(v.javaClass)
                                child.emitName(k.toString())
                                valueFormatter.prettyPrint(v, child)
                            }
                        }
                    }
                }
                return@putFormatter null
            }
        }

        class LazyFormatter<T>(val clazz: Class<T>) : Formatter<T> {
            override fun prettyPrint(subject: T, printer: Printer) {
                (formatters[clazz] as Formatter<T>).prettyPrint(subject, printer)
            }
        }

        fun prettyPrintPacket(packet: Packet<*>): Text {
            val sb = StringBuilder()
            getPrettyPrinter(packet.javaClass).prettyPrint(packet, AccumulatingPrinter(0, sb, false))
            return Text.literal(sb.toString())
        }

        fun <T> getPrettyPrinter(clazz: Class<T>): Formatter<T> {
            if (clazz in processing) {
                return LazyFormatter(clazz)
            }
            if (clazz.isEnum) {
                return object : Formatter<T> {
                    override fun prettyPrint(subject: T, printer: Printer) {
                        printer.emitValue(subject.toString())
                    }
                }
            }
            return formatters.getOrPut(clazz) {
                try {
                    processing.add(clazz)

                    val dynamic = dynamicFormatter.firstNotNullOfOrNull { dynamic -> dynamic(clazz) }
                    if (dynamic != null) {
                        return@getOrPut dynamic
                    }

                    val fields = clazz.declaredFields
                        .filter { !it.isStatic }
                        .onEach { it.isAccessible = true }
                        .associateWith { getPrettyPrinter(it.type) }
                    val name = clazz.simpleName // TODO: remapping
                    object : Formatter<T> {
                        override fun prettyPrint(subject: T, printer: Printer) {
                            val child = printer.beginObject(name)
                            fun <V> printField(value: V, formatter: Formatter<V>) {
                                formatter.prettyPrint(value, child)
                            }
                            for ((field, fieldFormatter) in fields) {
                                val value = field.get(subject)
                                child.emitName(field.name) // TODO: remapping
                                // We love `Nothing` being unable to be cast away
                                printField(value as Any, fieldFormatter as Formatter<Any>)
                            }
                        }
                    }
                } finally {
                    processing.remove(clazz)
                }
            } as Formatter<T>
        }

    }
}