diff --git a/critter/core/src/main/kotlin/dev/morphia/critter/parser/java/CritterClassLoader.kt b/critter/core/src/main/kotlin/dev/morphia/critter/parser/java/CritterClassLoader.kt index 2c108554727..72ee71ccd2e 100644 --- a/critter/core/src/main/kotlin/dev/morphia/critter/parser/java/CritterClassLoader.kt +++ b/critter/core/src/main/kotlin/dev/morphia/critter/parser/java/CritterClassLoader.kt @@ -17,9 +17,7 @@ class CritterClassLoader(parent: ClassLoader?) : ChildFirst(parent, mapOf()), Cl fun dump(output: String) { typeDefinitions.forEach { (name, bytes) -> val name1 = File(name.replace('.', '/')).name - val file = File(output, "$name1.class") - println("**************** dumping '$name' to file $file") - FileOutputStream(file).write(bytes) + FileOutputStream(File(output, "$name1.class")).write(bytes) } } } diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/parser/TestAsmGenerator.kt b/critter/core/src/test/kotlin/dev/morphia/critter/parser/TestAsmGenerator.kt index 14eef4e431c..2cf9aaebf50 100644 --- a/critter/core/src/test/kotlin/dev/morphia/critter/parser/TestAsmGenerator.kt +++ b/critter/core/src/test/kotlin/dev/morphia/critter/parser/TestAsmGenerator.kt @@ -5,33 +5,45 @@ import dev.morphia.critter.parser.generators.EntityTypeUpdate import dev.morphia.critter.parser.java.CritterClassLoader import dev.morphia.critter.parser.java.CritterParser.critterClassLoader import dev.morphia.critter.sources.DummyEntity +import dev.morphia.critter.sources.KotlinDummyEntity +import ksp.org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf.className import org.bson.codecs.pojo.PropertyAccessor import org.testng.Assert.assertEquals import org.testng.Assert.assertTrue +import org.testng.annotations.DataProvider import org.testng.annotations.Test class TestAsmGenerator { - @Test - fun testNestedClass() { + companion object { + val EARLY = false + } + + @Test(dataProvider = "classes") + fun testNestedClass(type: Class<*>) { val critterClassLoader = CritterClassLoader(Thread.currentThread().contextClassLoader) val bytes = - EntityTypeUpdate("dev.morphia.critter.sources.DummyEntity") - .update(mapOf("name" to String::class.java)) - critterClassLoader.register("dev.morphia.critter.sources.DummyEntity", bytes) + EntityTypeUpdate(type.name) + .update(mapOf("name" to String::class.java, "age" to Int::class.java)) + critterClassLoader.register(type.name, bytes) critterClassLoader.dump("target") - val entity = - critterClassLoader - .loadClass("dev.morphia.critter.sources.DummyEntity") - .getConstructor() - .newInstance() + val entity = critterClassLoader.loadClass(type.name).getConstructor().newInstance() + + testStringType(type, critterClassLoader, entity) + testPrimitiveType(type, critterClassLoader, entity) + } - val generator = EntityAccessorGenerator(DummyEntity::class.java, "name", String::class.java) + private fun testStringType( + type: Class<*>, + critterClassLoader: CritterClassLoader, + entity: Any, + ) { + val generator = EntityAccessorGenerator(type, "name", String::class.java) critterClassLoader.register(generator.accessorType.className, generator.dump()) critterClassLoader.dump("target") - + if (EARLY) return val type = generator.accessorType.className var accessor = (critterClassLoader.loadClass(type) as Class>) @@ -43,4 +55,31 @@ class TestAsmGenerator { assertEquals(accessor.get(entity), "set externally") assertTrue(entity.toString().contains("set externally"), entity.toString()) } + + private fun testPrimitiveType( + type: Class<*>, + critterClassLoader: CritterClassLoader, + entity: Any, + ) { + val generator = EntityAccessorGenerator(type, "age", Int::class.java) + critterClassLoader.register(generator.accessorType.className, generator.dump()) + + critterClassLoader.dump("target") + if (EARLY) return + val type = generator.accessorType.className + var accessor = + (critterClassLoader.loadClass(type) as Class>) + .getConstructor() + .newInstance() + + accessor.set(entity, 100) + + assertEquals(accessor.get(entity), 100) + assertTrue(entity.toString().contains("100"), entity.toString()) + } + + @DataProvider(name = "classes") + fun names(): Array> { + return arrayOf(DummyEntity::class.java, KotlinDummyEntity::class.java) + } } diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityAccessorGenerator.kt b/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityAccessorGenerator.kt index 043401d7b8f..294be2119ac 100644 --- a/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityAccessorGenerator.kt +++ b/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityAccessorGenerator.kt @@ -9,6 +9,7 @@ import org.objectweb.asm.Type class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: Class<*>) { val entityType: Type = Type.getType(host) val fieldType = Type.getType(fieldClass) + val wrapped = wrap(fieldType) val classWriter = ClassWriter(0) val accessorName = "${host.packageName.replace('.', '/')}/__morphia/${host.simpleName}${fieldName.titleCase()}Accessor" @@ -19,7 +20,7 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: V17, ACC_PUBLIC or ACC_SUPER, accessorType.internalName, - "Ljava/lang/Object;Lorg/bson/codecs/pojo/PropertyAccessor<${fieldType.descriptor}>;", + "Ljava/lang/Object;Lorg/bson/codecs/pojo/PropertyAccessor<${wrap(fieldType).descriptor}>;", "java/lang/Object", arrayOf("org/bson/codecs/pojo/PropertyAccessor") ) @@ -34,6 +35,21 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: return classWriter.toByteArray() } + private fun wrap(fieldType: Type): Type { + return when (fieldType) { + Type.VOID_TYPE -> Type.getType(Void::class.java) + Type.BOOLEAN_TYPE -> Type.getType(Boolean::class.java) + Type.CHAR_TYPE -> Type.getType(Char::class.java) + Type.BYTE_TYPE -> Type.getType(Byte::class.java) + Type.SHORT_TYPE -> Type.getType(Short::class.java) + Type.INT_TYPE -> Type.getType(Integer::class.java) + Type.FLOAT_TYPE -> Type.getType(Float::class.java) + Type.LONG_TYPE -> Type.getType(Long::class.java) + Type.DOUBLE_TYPE -> Type.getType(Double::class.java) + else -> fieldType + } + } + private fun getBridge() { val methodVisitor = classWriter.visitMethod( @@ -53,7 +69,7 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: INVOKEVIRTUAL, accessorType.internalName, "get", - "(Ljava/lang/Object;)${fieldType.descriptor}", + "(Ljava/lang/Object;)${wrapped.descriptor}", false ) methodVisitor.visitInsn(ARETURN) @@ -80,12 +96,12 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: mv.visitVarInsn(ALOAD, 0) mv.visitVarInsn(ALOAD, 1) mv.visitVarInsn(ALOAD, 2) - mv.visitTypeInsn(CHECKCAST, fieldType.internalName) + mv.visitTypeInsn(CHECKCAST, wrapped.internalName) mv.visitMethodInsn( INVOKEVIRTUAL, accessorType.internalName, "set", - "(Ljava/lang/Object;${fieldType.descriptor})V", + "(Ljava/lang/Object;${wrapped.descriptor})V", false ) mv.visitInsn(RETURN) @@ -101,8 +117,8 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: classWriter.visitMethod( ACC_PUBLIC, "set", - "(Ljava/lang/Object;${fieldType.descriptor})V", - "(TS;${fieldType.descriptor})V", + "(Ljava/lang/Object;${wrapped.descriptor})V", + "(TS;${wrapped.descriptor})V", null ) mv.visitCode() @@ -112,10 +128,19 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: mv.visitVarInsn(ALOAD, 1) mv.visitTypeInsn(CHECKCAST, entityType.internalName) mv.visitVarInsn(ALOAD, 2) + if (!wrapped.equals(fieldType)) { + mv.visitMethodInsn( + INVOKEVIRTUAL, + wrapped.internalName, + "${fieldType.className}Value", + "()${fieldType.descriptor}", + false + ) + } mv.visitMethodInsn( INVOKEVIRTUAL, entityType.internalName, - "__writeName", + "__write${fieldName.titleCase()}", "(${fieldType.descriptor})V", false ) @@ -137,8 +162,8 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: classWriter.visitMethod( ACC_PUBLIC, "get", - "(Ljava/lang/Object;)${fieldType.descriptor}", - "(TS;)${fieldType.descriptor}", + "(Ljava/lang/Object;)${wrapped.descriptor}", + "(TS;)${wrapped.descriptor}", null ) mv.visitCode() @@ -150,10 +175,19 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass: mv.visitMethodInsn( INVOKEVIRTUAL, entityType.internalName, - "__readName", + "__read${fieldName.titleCase()}", "()${fieldType.descriptor}", false ) + if (!wrapped.equals(fieldType)) { + mv.visitMethodInsn( + INVOKESTATIC, + wrapped.internalName, + "valueOf", + "(${fieldType.descriptor})${wrapped.descriptor}", + false + ) + } mv.visitInsn(ARETURN) val label1 = Label() mv.visitLabel(label1) diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityTypeUpdate.kt b/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityTypeUpdate.kt index a8dac646e15..e05608e647b 100644 --- a/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityTypeUpdate.kt +++ b/critter/core/src/test/kotlin/dev/morphia/critter/parser/generators/EntityTypeUpdate.kt @@ -1,5 +1,6 @@ package dev.morphia.critter.parser.generators +import dev.morphia.critter.parser.titleCase import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter @@ -7,8 +8,9 @@ import org.objectweb.asm.Label import org.objectweb.asm.Opcodes.ACC_PUBLIC import org.objectweb.asm.Opcodes.ACC_SYNTHETIC import org.objectweb.asm.Opcodes.ALOAD -import org.objectweb.asm.Opcodes.ARETURN import org.objectweb.asm.Opcodes.GETFIELD +import org.objectweb.asm.Opcodes.ILOAD +import org.objectweb.asm.Opcodes.IRETURN import org.objectweb.asm.Opcodes.PUTFIELD import org.objectweb.asm.Opcodes.RETURN import org.objectweb.asm.Type @@ -23,9 +25,9 @@ class EntityTypeUpdate(val entity: String) { cr.accept(writer, 0) fields.forEach { (name, type) -> - // val fieldType = Type.getType(type) - read(writer, name, type.descriptorString()) - write(writer, name, type.descriptorString()) + val fieldType = Type.getType(type) + read(writer, name, fieldType) + write(writer, name, fieldType) } writer.visitEnd() @@ -34,12 +36,12 @@ class EntityTypeUpdate(val entity: String) { return writer.toByteArray() } - private fun write(classNode: ClassVisitor, field: String, fieldDescriptor: String) { + private fun write(classNode: ClassVisitor, field: String, fieldType: Type) { val mv = classNode.visitMethod( ACC_PUBLIC or ACC_SYNTHETIC, - "__writeName", - "(${fieldDescriptor})V", + "__write${field.titleCase()}", + "(${fieldType.descriptor})V", null, null ) @@ -48,8 +50,8 @@ class EntityTypeUpdate(val entity: String) { mv.visitLabel(label0) mv.visitLineNumber(18, label0) mv.visitVarInsn(ALOAD, 0) - mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTFIELD, entityType.internalName, field, fieldDescriptor) + mv.visitVarInsn(fieldType.getOpcode(ILOAD), 1) + mv.visitFieldInsn(PUTFIELD, entityType.internalName, field, fieldType.descriptor) val label1 = Label() mv.visitLabel(label1) mv.visitLineNumber(19, label1) @@ -57,17 +59,17 @@ class EntityTypeUpdate(val entity: String) { val label2 = Label() mv.visitLabel(label2) mv.visitLocalVariable("this", entityType.descriptor, null, label0, label2, 0) - mv.visitLocalVariable("value", fieldDescriptor, null, label0, label2, 1) + mv.visitLocalVariable("value", fieldType.descriptor, null, label0, label2, 1) mv.visitMaxs(2, 2) mv.visitEnd() } - private fun read(classNode: ClassVisitor, field: String, fieldDescriptor: String) { + private fun read(classNode: ClassVisitor, field: String, fieldType: Type) { val mv = classNode.visitMethod( ACC_PUBLIC or ACC_SYNTHETIC, - "__readName", - "()${fieldDescriptor}", + "__read${field.titleCase()}", + "()${fieldType.descriptor}", null, null ) @@ -76,8 +78,8 @@ class EntityTypeUpdate(val entity: String) { mv.visitLabel(label0) mv.visitLineNumber(14, label0) mv.visitVarInsn(ALOAD, 0) - mv.visitFieldInsn(GETFIELD, entityType.internalName, field, fieldDescriptor) - mv.visitInsn(ARETURN) + mv.visitFieldInsn(GETFIELD, entityType.internalName, field, fieldType.descriptor) + mv.visitInsn(fieldType.getOpcode(IRETURN)) val label1: Label = Label() mv.visitLabel(label1) mv.visitLocalVariable("this", entityType.descriptor, null, label0, label1, 0) diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntity.java b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntity.java index efaf454da31..47348b0488e 100644 --- a/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntity.java +++ b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntity.java @@ -3,19 +3,30 @@ public class DummyEntity { private String name = "DummyEntity"; + private int age = 21; + @Override public String toString() { return "DummyEntity{" + "name='" + name + '\'' + + ", age=" + age + '}'; } -// public String __readName() { -// return name; -// } -// -// public void __writeName(final String name) { -// this.name = name; -// } + public String __readNameSample() { + return name; + } + + public void __writeNameSample(final String name) { + this.name = name; + } + + public int __readAgeSample() { + return age; + } + + public void __writeAgeSample(final int age) { + this.age = age; + } } diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityAgeAccessorModel.java b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityAgeAccessorModel.java new file mode 100644 index 00000000000..a20f467d570 --- /dev/null +++ b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityAgeAccessorModel.java @@ -0,0 +1,14 @@ +package dev.morphia.critter.sources; + +import org.bson.codecs.pojo.PropertyAccessor; + +public class DummyEntityAgeAccessorModel implements PropertyAccessor { + @Override + public Integer get(S entity) { + return ((DummyEntity) entity).__readAgeSample(); + } + + public void set(S entity, Integer value) { + ((DummyEntity) entity).__writeAgeSample(value); + } +} diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityNameAccessorModel.java b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityNameAccessorModel.java index cca0efb98df..16f1521d870 100644 --- a/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityNameAccessorModel.java +++ b/critter/core/src/test/kotlin/dev/morphia/critter/sources/DummyEntityNameAccessorModel.java @@ -5,11 +5,11 @@ public class DummyEntityNameAccessorModel implements PropertyAccessor { @Override public String get(S entity) { - return null;// ((DummyEntity) entity).__readName(); + return ((DummyEntity) entity).__readNameSample(); } @Override public void set(S entity, String value) { -// ((DummyEntity) entity).__writeName(value); + ((DummyEntity) entity).__writeNameSample(value); } } diff --git a/critter/core/src/test/kotlin/dev/morphia/critter/sources/KotlinDummyEntity.kt b/critter/core/src/test/kotlin/dev/morphia/critter/sources/KotlinDummyEntity.kt new file mode 100644 index 00000000000..2170b44f682 --- /dev/null +++ b/critter/core/src/test/kotlin/dev/morphia/critter/sources/KotlinDummyEntity.kt @@ -0,0 +1,10 @@ +package dev.morphia.critter.sources + +class KotlinDummyEntity { + private var name = "DummyEntity" + private var age = 21 + + override fun toString(): String { + return "KotlinDummyEntity(name='$name', age=$age)" + } +}