Skip to content

Commit

Permalink
add support for primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
evanchooly committed Jul 2, 2024
1 parent 80fb145 commit 0c84085
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<PropertyAccessor<String>>)
Expand All @@ -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<PropertyAccessor<Int>>)
.getConstructor()
.newInstance()

accessor.set(entity, 100)

assertEquals(accessor.get(entity), 100)
assertTrue(entity.toString().contains("100"), entity.toString())
}

@DataProvider(name = "classes")
fun names(): Array<Class<out Any>> {
return arrayOf(DummyEntity::class.java, KotlinDummyEntity::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
)
Expand All @@ -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(
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -101,8 +117,8 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass:
classWriter.visitMethod(
ACC_PUBLIC,
"set",
"(Ljava/lang/Object;${fieldType.descriptor})V",
"<S:Ljava/lang/Object;>(TS;${fieldType.descriptor})V",
"(Ljava/lang/Object;${wrapped.descriptor})V",
"<S:Ljava/lang/Object;>(TS;${wrapped.descriptor})V",
null
)
mv.visitCode()
Expand All @@ -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
)
Expand All @@ -137,8 +162,8 @@ class EntityAccessorGenerator(host: Class<*>, val fieldName: String, fieldClass:
classWriter.visitMethod(
ACC_PUBLIC,
"get",
"(Ljava/lang/Object;)${fieldType.descriptor}",
"<S:Ljava/lang/Object;>(TS;)${fieldType.descriptor}",
"(Ljava/lang/Object;)${wrapped.descriptor}",
"<S:Ljava/lang/Object;>(TS;)${wrapped.descriptor}",
null
)
mv.visitCode()
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
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
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
Expand All @@ -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()
Expand All @@ -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
)
Expand All @@ -48,26 +50,26 @@ 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)
mv.visitInsn(RETURN)
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
)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.morphia.critter.sources;

import org.bson.codecs.pojo.PropertyAccessor;

public class DummyEntityAgeAccessorModel implements PropertyAccessor<Integer> {
@Override
public <S> Integer get(S entity) {
return ((DummyEntity) entity).__readAgeSample();
}

public <S> void set(S entity, Integer value) {
((DummyEntity) entity).__writeAgeSample(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
public class DummyEntityNameAccessorModel implements PropertyAccessor<String> {
@Override
public <S> String get(S entity) {
return null;// ((DummyEntity) entity).__readName();
return ((DummyEntity) entity).__readNameSample();
}

@Override
public <S> void set(S entity, String value) {
// ((DummyEntity) entity).__writeName(value);
((DummyEntity) entity).__writeNameSample(value);
}
}
Original file line number Diff line number Diff line change
@@ -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)"
}
}

0 comments on commit 0c84085

Please sign in to comment.