Skip to content

Commit 285d422

Browse files
evanchoolyclaude
andauthored
Add VarHandle-based accessor generator for runtime mapper mode (#4194)
Introduces VarHandleAccessorGenerator, which generates PropertyAccessor implementations using VarHandle (fields) and MethodHandle (method-based properties) instead of delegating to __readXxx/__writeXxx synthetic methods. This enables runtime-mode mapping without modifying entity bytecode on the classpath, as required by CritterMapper (Phase 4). - VarHandleAccessorGenerator: Gizmo-based generator using MethodHandles.privateLookupIn() to create VarHandle/MethodHandle fields in the constructor; entity class loaded via TCCL to avoid CritterClassLoader classloader mismatch - PropertyFinder: runtimeMode flag skips entity bytecode registration and routes to VarHandleAccessorGenerator instead of PropertyAccessorGenerator - CritterGizmoGenerator: runtimeMode param threaded through to PropertyFinder; varHandleAccessor() factory methods added - TestVarHandleAccessor: covers String, int primitive, Long boxed fields; verifies entity class is not modified in runtime mode Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fa7c7b2 commit 285d422

File tree

4 files changed

+535
-6
lines changed

4 files changed

+535
-6
lines changed

critter/core/src/main/kotlin/dev/morphia/critter/parser/PropertyFinder.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator.accessor
55
import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator.fieldAccessors
66
import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator.methodAccessors
77
import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator.propertyModelGenerator
8+
import dev.morphia.critter.parser.gizmo.CritterGizmoGenerator.varHandleAccessor
89
import dev.morphia.critter.parser.gizmo.PropertyModelGenerator
910
import dev.morphia.critter.parser.java.CritterParser
1011
import dev.morphia.mapping.Mapper
@@ -14,7 +15,11 @@ import org.objectweb.asm.tree.AnnotationNode
1415
import org.objectweb.asm.tree.ClassNode
1516
import org.objectweb.asm.tree.FieldNode
1617

17-
class PropertyFinder(mapper: Mapper, val classLoader: CritterClassLoader) {
18+
class PropertyFinder(
19+
mapper: Mapper,
20+
val classLoader: CritterClassLoader,
21+
val runtimeMode: Boolean = false,
22+
) {
1823
private val providerMap =
1924
mapper.config.propertyAnnotationProviders().associateBy { it.provides() }
2025

@@ -23,15 +28,21 @@ class PropertyFinder(mapper: Mapper, val classLoader: CritterClassLoader) {
2328
val methods = discoverPropertyMethods(classNode)
2429
if (methods.isEmpty()) {
2530
val fields = discoverAllFields(entityType, classNode)
26-
classLoader.register(entityType.name, fieldAccessors(entityType, fields))
31+
if (!runtimeMode) {
32+
classLoader.register(entityType.name, fieldAccessors(entityType, fields))
33+
}
2734
fields.forEach { field ->
28-
accessor(entityType, classLoader, field)
35+
if (runtimeMode) varHandleAccessor(entityType, classLoader, field)
36+
else accessor(entityType, classLoader, field)
2937
models += propertyModelGenerator(entityType, classLoader, field)
3038
}
3139
} else {
32-
classLoader.register(entityType.name, methodAccessors(entityType, methods))
40+
if (!runtimeMode) {
41+
classLoader.register(entityType.name, methodAccessors(entityType, methods))
42+
}
3343
methods.forEach { method ->
34-
accessor(entityType, classLoader, method)
44+
if (runtimeMode) varHandleAccessor(entityType, classLoader, method)
45+
else accessor(entityType, classLoader, method)
3546
models += propertyModelGenerator(entityType, classLoader, method)
3647
}
3748
}

critter/core/src/main/kotlin/dev/morphia/critter/parser/gizmo/CritterGizmoGenerator.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ object CritterGizmoGenerator {
1414
fun generate(
1515
type: Class<*>,
1616
critterClassLoader: CritterClassLoader = CritterClassLoader(),
17+
runtimeMode: Boolean = false,
1718
): GizmoEntityModelGenerator {
1819
val classNode = ClassNode()
1920
val resourceName = type.name.replace('.', '/') + ".class"
2021
val inputStream =
2122
type.classLoader.getResourceAsStream(resourceName)
2223
?: throw IllegalArgumentException("Could not find class file for ${type.name}")
2324
ClassReader(inputStream).accept(classNode, 0)
24-
val propertyFinder = PropertyFinder(Generators.mapper, critterClassLoader)
25+
val propertyFinder = PropertyFinder(Generators.mapper, critterClassLoader, runtimeMode)
2526

2627
return entityModel(
2728
type,
@@ -43,6 +44,18 @@ object CritterGizmoGenerator {
4344
fun accessor(entityType: Class<*>, critterClassLoader: CritterClassLoader, method: MethodNode) =
4445
PropertyAccessorGenerator(entityType, critterClassLoader, method).emit()
4546

47+
fun varHandleAccessor(
48+
entityType: Class<*>,
49+
critterClassLoader: CritterClassLoader,
50+
field: FieldNode,
51+
) = VarHandleAccessorGenerator(entityType, critterClassLoader, field).emit()
52+
53+
fun varHandleAccessor(
54+
entityType: Class<*>,
55+
critterClassLoader: CritterClassLoader,
56+
method: MethodNode,
57+
) = VarHandleAccessorGenerator(entityType, critterClassLoader, method).emit()
58+
4659
fun propertyModelGenerator(
4760
entityType: Class<*>,
4861
critterClassLoader: CritterClassLoader,

0 commit comments

Comments
 (0)