Skip to content

Commit fffc05a

Browse files
committed
Kotlin dynamic reflection serializer
1 parent 80a3d38 commit fffc05a

File tree

2 files changed

+49
-50
lines changed

2 files changed

+49
-50
lines changed

common/src/main/kotlin/com/lambda/util/DynamicReflectionSerializer.kt

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,35 +38,39 @@ import net.minecraft.util.Identifier
3838
import net.minecraft.util.math.BlockPos
3939
import net.minecraft.util.math.ChunkPos
4040
import org.apache.logging.log4j.Logger
41-
import java.lang.reflect.Field
4241
import java.lang.reflect.InaccessibleObjectException
4342
import java.util.*
4443
import kotlin.jvm.optionals.getOrDefault
44+
import kotlin.reflect.KClass
45+
import kotlin.reflect.KProperty1
46+
import kotlin.reflect.full.memberProperties
47+
import kotlin.reflect.jvm.javaField
48+
import kotlin.reflect.jvm.jvmErasure
4549

4650
object DynamicReflectionSerializer : Loadable {
4751
// Classes that should not be recursively serialized
4852
private val skipables = setOf(
49-
Codec::class.java,
50-
Logger::class.java,
51-
BlockPos::class.java,
52-
BlockState::class.java,
53-
ItemStack::class.java,
54-
Identifier::class.java,
55-
NbtCompound::class.java,
56-
Map::class.java,
57-
BitSet::class.java,
58-
Collection::class.java,
59-
RegistryEntry::class.java,
60-
RegistryKey::class.java,
61-
ScreenHandlerType::class.java,
62-
TranslationStorage::class.java,
63-
ChunkPos::class.java,
64-
Text::class.java,
65-
org.slf4j.Logger::class.java,
66-
String::class.java,
53+
Codec::class,
54+
Logger::class,
55+
BlockPos::class,
56+
BlockState::class,
57+
ItemStack::class,
58+
Identifier::class,
59+
NbtCompound::class,
60+
Map::class,
61+
BitSet::class,
62+
Collection::class,
63+
RegistryEntry::class,
64+
RegistryKey::class,
65+
ScreenHandlerType::class,
66+
TranslationStorage::class,
67+
ChunkPos::class,
68+
Text::class,
69+
org.slf4j.Logger::class,
70+
String::class,
6771
)
6872
private val skipFields = setOf(
69-
Codec::class.java,
73+
Codec::class,
7074
)
7175

7276
private const val INDENT = 2
@@ -104,9 +108,9 @@ object DynamicReflectionSerializer : Loadable {
104108

105109
val String.remappedName get() = mappings.getOrDefault(this, this)
106110

107-
fun <T : Any> Class<T>.dynamicName(remap: Boolean) =
108-
if (remap) canonicalName.remappedName else simpleName
109-
fun Field.dynamicName(remap: Boolean) =
111+
fun <T : Any> KClass<T>.dynamicName(remap: Boolean) =
112+
if (remap) qualifiedName?.remappedName else simpleName
113+
fun <T : Any> KProperty1<T, *>.dynamicName(remap: Boolean) =
110114
if (remap) name.remappedName else name
111115

112116
fun Any.dynamicString(
@@ -118,47 +122,46 @@ object DynamicReflectionSerializer : Loadable {
118122
remap: Boolean = !Lambda.isDebug,
119123
): String {
120124
if (visitedObjects.contains(this)) {
121-
builder.appendLine("$indent${javaClass.dynamicName(remap)} (Circular Reference)")
125+
builder.appendLine("$indent${this::class.dynamicName(remap)} (Circular Reference)")
122126
return builder.toString()
123127
}
124128

125129
visitedObjects.add(this)
126-
builder.appendLine("$indent${javaClass.dynamicName(remap)}")
130+
builder.appendLine("$indent${this::class.dynamicName(remap)}")
127131

128-
val fields = javaClass.declaredFields + javaClass.superclass?.declaredFields.orEmpty()
129-
fields.forEach { field ->
130-
processField(field, indent, builder, currentDepth, maxRecursionDepth, visitedObjects, remap)
131-
}
132+
this::class.memberProperties
133+
.forEach { processField(it, indent, builder, currentDepth, maxRecursionDepth, visitedObjects, remap) }
132134

133135
return builder.toString()
134136
}
135137

136-
private fun Any.processField(
137-
field: Field,
138+
private fun <T : Any> T.processField(
139+
field: KProperty1<out T, *>,
138140
indent: String,
139141
builder: StringBuilder,
140142
currentDepth: Int,
141143
maxRecursionDepth: Int,
142144
visitedObjects: MutableSet<Any>,
143145
remap: Boolean,
144146
) {
145-
if (skipFields.any { it.isAssignableFrom(field.type) }) return
147+
if (skipFields.any { it.isInstance(field) }) return
146148

147149
try {
148-
field.isAccessible = true
149-
} catch (e: InaccessibleObjectException) {
150+
field.javaField?.isAccessible = true
151+
} catch (_: InaccessibleObjectException) {
150152
return
151153
}
152-
val fieldValue = field.get(this)
154+
155+
val fieldValue = field.javaField?.get(this)
153156
val fieldIndent = "$indent${" ".repeat(INDENT)}"
154157
builder.appendLine("$fieldIndent${field.dynamicName(remap)}: ${fieldValue.formatFieldValue(remap)}")
155158

156159
if (currentDepth < maxRecursionDepth
157160
&& fieldValue != null
158-
&& !field.type.isPrimitive
159-
&& !field.type.isArray
160-
&& !field.type.isEnum
161-
&& skipables.none { it.isAssignableFrom(field.type) }
161+
&& !field.returnType.jvmErasure.java.isPrimitive
162+
&& !field.returnType.jvmErasure.java.isArray
163+
&& !field.returnType.jvmErasure.java.isEnum
164+
&& skipables.none { it.isInstance(field.returnType.jvmErasure) }
162165
) {
163166
fieldValue.dynamicString(
164167
maxRecursionDepth,
@@ -186,10 +189,10 @@ object DynamicReflectionSerializer : Loadable {
186189
is Identifier -> "$namespace:$path"
187190
is NbtCompound -> asString().getOrDefault("")
188191
is RegistryEntry<*> -> "${value()}"
192+
null -> "null"
189193
else -> {
190-
if (this?.javaClass?.canonicalName?.contains("minecraft") == true)
191-
"${this.javaClass.dynamicName(remap)}@${Integer.toHexString(hashCode())}"
192-
else this?.toString() ?: "null"
194+
if (this::class.qualifiedName?.contains("minecraft") == true) "${this::class.dynamicName(remap)}@${Integer.toHexString(hashCode())}"
195+
else this.toString()
193196
}
194197
}
195198

common/src/main/kotlin/com/lambda/util/reflections/Reflections.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ import com.lambda.util.extension.isObject
2121
import com.lambda.util.extension.objectInstance
2222
import io.github.classgraph.ClassGraph
2323
import io.github.classgraph.ResourceList
24-
import org.reflections.util.ConfigurationBuilder
2524
import java.lang.reflect.Modifier
2625
import kotlin.jvm.java
2726

2827
/**
2928
* Retrieves all instances of the specified type `T`.
3029
*
3130
* @param T The type of instances to retrieve.
32-
* @param block A configuration lambda to customize the [ConfigurationBuilder] used to configure Reflections.
31+
* @param block A configuration lambda to customize the [ClassGraph] configuration.
3332
*
3433
* @return A list of instances of type `T`
3534
*/
@@ -50,15 +49,12 @@ inline fun <reified T : Any> getInstances(block: ClassGraph.() -> Unit = { enabl
5049
}
5150

5251
/**
53-
* Retrieves all resource paths that match the given pattern.
54-
*
55-
* The function caches the results based on the configuration provided via the [block] lambda to avoid redundant
56-
* reflection calls.
52+
* Retrieves all resource paths that match the given pattern wildcard.
5753
*
5854
* @param pattern The resource pattern to search for.
59-
* @param block A configuration lambda to customize the [ConfigurationBuilder] used to configure Reflections.
55+
* @param block A configuration lambda to customize the [ClassGraph] configuration.
6056
*
61-
* @return A set of resource paths that match the specified pattern.
57+
* @return A [ResourceList]
6258
*/
6359
inline fun getResources(pattern: String, block: ClassGraph.() -> Unit = { enableAllInfo(); acceptPackages("com.lambda") }): ResourceList =
6460
ClassGraph().apply(block)

0 commit comments

Comments
 (0)