ai.timefold.solver.quarkus.deployment.GizmoMemberAccessorEntityEnhancer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of timefold-solver-quarkus-deployment Show documentation
Show all versions of timefold-solver-quarkus-deployment Show documentation
Quarkus deployment module for timefold-solver-quarkus.
package ai.timefold.solver.quarkus.deployment;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.enterprise.context.ApplicationScoped;
import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorImplementor;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberDescriptor;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberInfo;
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoCloningUtils;
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionCloner;
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerFactory;
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerImplementor;
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionOrEntityDescriptor;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.quarkus.gizmo.TimefoldGizmoBeanFactory;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.BranchResult;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.DescriptorUtils;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.runtime.RuntimeValue;
final class GizmoMemberAccessorEntityEnhancer {
private final Set> visitedClasses = new HashSet<>();
// This keep track of fields we add virtual getters/setters for
private final Set visitedFields = new HashSet<>();
// This keep track of what fields we made non-final
private final Set visitedFinalFields = new HashSet<>();
private final Set visitedMethods = new HashSet<>();
private static String getVirtualGetterName(boolean isField, String name) {
return "$get$timefold$__" + ((isField) ? "field$__" : "method$__") + name;
}
private static String getVirtualSetterName(boolean isField, String name) {
return "$set$timefold$__" + ((isField) ? "field$__" : "method$__") + name;
}
/**
* Generates the bytecode for the member accessor for the specified field.
* Additionally enhances the class that declares the field with public simple
* getters/setters methods for the field if the field is private.
*
* @param annotationInstance The annotations on the field
* @param classOutput Where to output the bytecode
* @param fieldInfo The field to generate the MemberAccessor for
* @param transformers BuildProducer of BytecodeTransformers
*/
public String generateFieldAccessor(AnnotationInstance annotationInstance, ClassOutput classOutput, FieldInfo fieldInfo,
BuildProducer transformers) throws ClassNotFoundException, NoSuchFieldException {
Class> declaringClass = Class.forName(fieldInfo.declaringClass().name().toString(), false,
Thread.currentThread().getContextClassLoader());
Field fieldMember = declaringClass.getDeclaredField(fieldInfo.name());
GizmoMemberDescriptor member = createMemberDescriptorForField(fieldMember, transformers);
GizmoMemberInfo memberInfo = new GizmoMemberInfo(member, true,
(Class extends Annotation>) Class.forName(annotationInstance.name().toString(), false,
Thread.currentThread().getContextClassLoader()));
String generatedClassName = GizmoMemberAccessorFactory.getGeneratedClassName(fieldMember);
GizmoMemberAccessorImplementor.defineAccessorFor(generatedClassName, classOutput, memberInfo);
return generatedClassName;
}
private void addVirtualFieldGetter(Class> classInfo, Field fieldInfo,
BuildProducer transformers) {
if (!visitedFields.contains(fieldInfo)) {
transformers.produce(new BytecodeTransformerBuildItem(classInfo.getName(),
(className, classVisitor) -> new TimefoldFieldEnhancingClassVisitor(classInfo, classVisitor,
fieldInfo)));
visitedFields.add(fieldInfo);
}
}
private void makeFieldNonFinal(Field finalField, BuildProducer transformers) {
if (visitedFinalFields.contains(finalField)) {
return;
}
transformers.produce(new BytecodeTransformerBuildItem(finalField.getDeclaringClass().getName(),
(className, classVisitor) -> new TimefoldFinalFieldEnhancingClassVisitor(classVisitor, finalField)));
visitedFinalFields.add(finalField);
}
private static String getMemberName(Member member) {
return Objects.requireNonNullElse(ReflectionHelper.getGetterPropertyName(member), member.getName());
}
private static Optional getSetterDescriptor(ClassInfo classInfo, MethodInfo methodInfo, String name) {
if (methodInfo.name().startsWith("get") || methodInfo.name().startsWith("is")) {
// ex: for methodInfo = Integer getValue(), name = value,
// return void setValue(Integer value)
// i.e. capitalize first letter of name, and take a parameter
// of the getter return type.
return Optional.ofNullable(classInfo.method("set" + name.substring(0, 1)
.toUpperCase(Locale.ROOT) +
name.substring(1),
methodInfo.returnType())).map(MethodDescriptor::of);
} else {
return Optional.empty();
}
}
/**
* Generates the bytecode for the member accessor for the specified method.
* Additionally enhances the class that declares the method with public simple
* read/(optionally write if getter method and setter present) methods for the method
* if the method is private.
*
* @param annotationInstance The annotations on the field
* @param classOutput Where to output the bytecode
* @param classInfo The declaring class for the field
* @param methodInfo The method to generate the MemberAccessor for
* @param transformers BuildProducer of BytecodeTransformers
*/
public String generateMethodAccessor(AnnotationInstance annotationInstance, ClassOutput classOutput,
ClassInfo classInfo, MethodInfo methodInfo, boolean requiredReturnType,
BuildProducer transformers)
throws ClassNotFoundException, NoSuchMethodException {
Class> declaringClass = Class.forName(methodInfo.declaringClass().name().toString(), false,
Thread.currentThread().getContextClassLoader());
Method methodMember = declaringClass.getDeclaredMethod(methodInfo.name());
String generatedClassName = GizmoMemberAccessorFactory.getGeneratedClassName(methodMember);
GizmoMemberDescriptor member;
String name = getMemberName(methodMember);
Optional setterDescriptor = getSetterDescriptor(classInfo, methodInfo, name);
MethodDescriptor memberDescriptor = MethodDescriptor.of(methodInfo);
if (Modifier.isPublic(methodInfo.flags())) {
member = new GizmoMemberDescriptor(name, memberDescriptor, declaringClass, setterDescriptor.orElse(null));
} else {
setterDescriptor = addVirtualMethodGetter(classInfo, methodInfo, name, setterDescriptor, transformers);
String methodName = getVirtualGetterName(false, name);
MethodDescriptor newMethodDescriptor =
MethodDescriptor.ofMethod(declaringClass, methodName, memberDescriptor.getReturnType());
member = new GizmoMemberDescriptor(name, newMethodDescriptor, memberDescriptor, declaringClass,
setterDescriptor.orElse(null));
}
Class extends Annotation> annotationClass = null;
if (requiredReturnType || annotationInstance != null) {
annotationClass = (Class extends Annotation>) Class.forName(annotationInstance.name().toString(), false,
Thread.currentThread().getContextClassLoader());
}
GizmoMemberInfo memberInfo = new GizmoMemberInfo(member, requiredReturnType, annotationClass);
GizmoMemberAccessorImplementor.defineAccessorFor(generatedClassName, classOutput, memberInfo);
return generatedClassName;
}
private Optional addVirtualMethodGetter(ClassInfo classInfo, MethodInfo methodInfo, String name,
Optional setterDescriptor,
BuildProducer transformers) {
if (!visitedMethods.contains(methodInfo)) {
transformers.produce(new BytecodeTransformerBuildItem(classInfo.name().toString(),
(className, classVisitor) -> new TimefoldMethodEnhancingClassVisitor(classInfo, classVisitor, methodInfo,
name, setterDescriptor)));
visitedMethods.add(methodInfo);
}
return setterDescriptor.map(md -> MethodDescriptor
.ofMethod(classInfo.name().toString(), getVirtualSetterName(false, name),
md.getReturnType(), md.getParameterTypes()));
}
public String generateSolutionCloner(SolutionDescriptor solutionDescriptor, ClassOutput classOutput,
IndexView indexView, BuildProducer transformers) {
String generatedClassName = GizmoSolutionClonerFactory.getGeneratedClassName(solutionDescriptor);
try (ClassCreator classCreator = ClassCreator
.builder()
.className(generatedClassName)
.interfaces(GizmoSolutionCloner.class)
.classOutput(classOutput)
.setFinal(true)
.build()) {
Set> solutionSubclassSet =
indexView.getAllKnownSubclasses(DotName.createSimple(solutionDescriptor.getSolutionClass().getName()))
.stream()
.map(classInfo -> {
try {
return Class.forName(classInfo.name().toString(), false,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to find class (" + classInfo.name() +
"), which is a known subclass of the solution class (" +
solutionDescriptor.getSolutionClass() + ").", e);
}
}).collect(Collectors.toCollection(LinkedHashSet::new));
solutionSubclassSet.add(solutionDescriptor.getSolutionClass());
Map, GizmoSolutionOrEntityDescriptor> memoizedGizmoSolutionOrEntityDescriptorForClassMap =
new HashMap<>();
for (Class> solutionSubclass : solutionSubclassSet) {
getGizmoSolutionOrEntityDescriptorForEntity(solutionDescriptor,
solutionSubclass,
memoizedGizmoSolutionOrEntityDescriptorForClassMap,
transformers);
}
// IDEA gave error on entityClass being a Class...
for (Object entityClass : solutionDescriptor.getEntityClassSet()) {
getGizmoSolutionOrEntityDescriptorForEntity(solutionDescriptor,
(Class>) entityClass,
memoizedGizmoSolutionOrEntityDescriptorForClassMap,
transformers);
}
Set> solutionAndEntitySubclassSet = new HashSet<>(solutionSubclassSet);
for (Object entityClassObject : solutionDescriptor.getEntityClassSet()) {
Class> entityClass = (Class>) entityClassObject;
Collection classInfoCollection;
// getAllKnownSubclasses returns an empty collection for interfaces (silent failure); thus:
// for interfaces, we use getAllKnownImplementors; otherwise we use getAllKnownSubclasses
if (entityClass.isInterface()) {
classInfoCollection = indexView.getAllKnownImplementors(DotName.createSimple(entityClass.getName()));
} else {
classInfoCollection = indexView.getAllKnownSubclasses(DotName.createSimple(entityClass.getName()));
}
classInfoCollection.stream().map(classInfo -> {
try {
return Class.forName(classInfo.name().toString(), false,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to find class (" + classInfo.name() +
"), which is a known subclass of the entity class (" +
entityClass + ").", e);
}
}).forEach(solutionAndEntitySubclassSet::add);
}
Set> deepClonedClassSet =
GizmoCloningUtils.getDeepClonedClasses(solutionDescriptor, solutionAndEntitySubclassSet);
for (Class> deepCloningClass : deepClonedClassSet) {
makeConstructorAccessible(deepCloningClass, transformers);
if (!memoizedGizmoSolutionOrEntityDescriptorForClassMap.containsKey(deepCloningClass)) {
getGizmoSolutionOrEntityDescriptorForEntity(solutionDescriptor,
deepCloningClass,
memoizedGizmoSolutionOrEntityDescriptorForClassMap,
transformers);
}
}
GizmoSolutionClonerImplementor.defineClonerFor(QuarkusGizmoSolutionClonerImplementor::new,
classCreator,
solutionDescriptor, solutionSubclassSet,
memoizedGizmoSolutionOrEntityDescriptorForClassMap, deepClonedClassSet);
}
return generatedClassName;
}
private void makeConstructorAccessible(Class> clazz, BuildProducer transformers) {
try {
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
return;
}
Constructor> constructor = clazz.getDeclaredConstructor();
if (!Modifier.isPublic(constructor.getModifiers()) && !visitedClasses.contains(clazz)) {
transformers.produce(new BytecodeTransformerBuildItem(clazz.getName(),
(className, classVisitor) -> new TimefoldConstructorEnhancingClassVisitor(classVisitor)));
visitedClasses.add(clazz);
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"Class (" + clazz.getName() + ") must have a no-args constructor so it can be constructed by Timefold.");
}
}
private GizmoSolutionOrEntityDescriptor getGizmoSolutionOrEntityDescriptorForEntity(
SolutionDescriptor solutionDescriptor, Class> entityClass,
Map, GizmoSolutionOrEntityDescriptor> memoizedMap,
BuildProducer transformers) {
Map solutionFieldToMemberDescriptor = new HashMap<>();
Class> currentClass = entityClass;
while (currentClass != null) {
for (Field field : currentClass.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
solutionFieldToMemberDescriptor.put(field, createMemberDescriptorForField(field, transformers));
}
currentClass = currentClass.getSuperclass();
}
GizmoSolutionOrEntityDescriptor out =
new GizmoSolutionOrEntityDescriptor(solutionDescriptor, entityClass, solutionFieldToMemberDescriptor);
memoizedMap.put(entityClass, out);
return out;
}
private GizmoMemberDescriptor createMemberDescriptorForField(Field field,
BuildProducer transformers) {
if (Modifier.isFinal(field.getModifiers())) {
makeFieldNonFinal(field, transformers);
}
Class> declaringClass = field.getDeclaringClass();
FieldDescriptor memberDescriptor = FieldDescriptor.of(field);
String name = field.getName();
// Not being recorded, so can use Type and annotated element directly
if (Modifier.isPublic(field.getModifiers())) {
return new GizmoMemberDescriptor(name, memberDescriptor, declaringClass);
} else {
addVirtualFieldGetter(declaringClass, field, transformers);
String getterName = getVirtualGetterName(true, name);
MethodDescriptor getterDescriptor =
MethodDescriptor.ofMethod(declaringClass.getName(), getterName, field.getType());
String setterName = getVirtualSetterName(true, name);
MethodDescriptor setterDescriptor =
MethodDescriptor.ofMethod(declaringClass.getName(), setterName, "void", field.getType());
return new GizmoMemberDescriptor(name, getterDescriptor, memberDescriptor, declaringClass, setterDescriptor);
}
}
public static Map> getGeneratedGizmoMemberAccessorMap(RecorderContext recorderContext,
Set generatedMemberAccessorsClassNames) {
Map> generatedGizmoMemberAccessorNameToInstanceMap = new HashMap<>();
for (String className : generatedMemberAccessorsClassNames) {
generatedGizmoMemberAccessorNameToInstanceMap.put(className, recorderContext.newInstance(className));
}
return generatedGizmoMemberAccessorNameToInstanceMap;
}
public static Map> getGeneratedSolutionClonerMap(RecorderContext recorderContext,
Set generatedSolutionClonersClassNames) {
Map> generatedGizmoSolutionClonerNameToInstanceMap = new HashMap<>();
for (String className : generatedSolutionClonersClassNames) {
generatedGizmoSolutionClonerNameToInstanceMap.put(className, recorderContext.newInstance(className));
}
return generatedGizmoSolutionClonerNameToInstanceMap;
}
public String generateGizmoBeanFactory(ClassOutput classOutput, Set> beanClasses,
BuildProducer transformers) {
String generatedClassName = TimefoldGizmoBeanFactory.class.getName() + "$Implementation";
ClassCreator classCreator = ClassCreator
.builder()
.className(generatedClassName)
.interfaces(TimefoldGizmoBeanFactory.class)
.classOutput(classOutput)
.build();
classCreator.addAnnotation(ApplicationScoped.class);
MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.ofMethod(TimefoldGizmoBeanFactory.class,
"newInstance", Object.class, Class.class));
ResultHandle query = methodCreator.getMethodParam(0);
BytecodeCreator currentBranch = methodCreator;
for (Class> beanClass : beanClasses) {
if (beanClass.isInterface() || Modifier.isAbstract(beanClass.getModifiers())) {
continue;
}
makeConstructorAccessible(beanClass, transformers);
ResultHandle beanClassHandle = currentBranch.loadClass(beanClass);
ResultHandle isTarget = currentBranch.invokeVirtualMethod(
MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class),
beanClassHandle, query);
BranchResult isQueryBranchResult = currentBranch.ifTrue(isTarget);
BytecodeCreator isQueryBranch = isQueryBranchResult.trueBranch();
ResultHandle beanInstance =
isQueryBranch.newInstance(MethodDescriptor.ofConstructor(beanClass));
isQueryBranch.returnValue(beanInstance);
currentBranch = isQueryBranchResult.falseBranch();
}
currentBranch.returnValue(currentBranch.loadNull());
classCreator.close();
return generatedClassName;
}
private static class TimefoldFinalFieldEnhancingClassVisitor extends ClassVisitor {
final Field finalField;
public TimefoldFinalFieldEnhancingClassVisitor(ClassVisitor outputClassVisitor, Field finalField) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
this.finalField = finalField;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(finalField.getName())) {
// x & ~bitFlag = x without bitFlag set
return super.visitField(access & ~Opcodes.ACC_FINAL, name, descriptor, signature, value);
} else {
return super.visitField(access, name, descriptor, signature, value);
}
}
}
private static class TimefoldConstructorEnhancingClassVisitor extends ClassVisitor {
public TimefoldConstructorEnhancingClassVisitor(ClassVisitor outputClassVisitor) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
}
@Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (name.equals("")) {
return cv.visitMethod(
ACC_PUBLIC,
name,
desc,
signature,
exceptions);
}
return cv.visitMethod(
access, name, desc, signature, exceptions);
}
}
private static class TimefoldFieldEnhancingClassVisitor extends ClassVisitor {
private final Field fieldInfo;
private final Class> clazz;
private final String fieldTypeDescriptor;
public TimefoldFieldEnhancingClassVisitor(Class> classInfo, ClassVisitor outputClassVisitor,
Field fieldInfo) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
this.fieldInfo = fieldInfo;
clazz = classInfo;
fieldTypeDescriptor = Type.getDescriptor(fieldInfo.getType());
}
@Override
public void visitEnd() {
super.visitEnd();
addGetter(this.cv);
addSetter(this.cv);
}
private void addSetter(ClassVisitor classWriter) {
String methodName = getVirtualSetterName(true, fieldInfo.getName());
MethodVisitor mv;
mv = classWriter.visitMethod(ACC_PUBLIC, methodName, "(" + fieldTypeDescriptor + ")V",
null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(Type.getType(fieldTypeDescriptor).getOpcode(ILOAD), 1);
mv.visitFieldInsn(PUTFIELD, Type.getInternalName(clazz), fieldInfo.getName(), fieldTypeDescriptor);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
}
private void addGetter(ClassVisitor classWriter) {
String methodName = getVirtualGetterName(true, fieldInfo.getName());
MethodVisitor mv;
mv = classWriter.visitMethod(ACC_PUBLIC, methodName, "()" + fieldTypeDescriptor,
null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, Type.getInternalName(clazz), fieldInfo.getName(), fieldTypeDescriptor);
mv.visitInsn(Type.getType(fieldTypeDescriptor).getOpcode(IRETURN));
mv.visitMaxs(0, 0);
}
}
private static class TimefoldMethodEnhancingClassVisitor extends ClassVisitor {
private final MethodInfo methodInfo;
private final Class> clazz;
private final String returnTypeDescriptor;
private final MethodDescriptor setter;
private final String name;
public TimefoldMethodEnhancingClassVisitor(ClassInfo classInfo, ClassVisitor outputClassVisitor,
MethodInfo methodInfo, String name, Optional maybeSetter) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
this.methodInfo = methodInfo;
this.name = name;
this.setter = maybeSetter.orElse(null);
try {
clazz = Class.forName(classInfo.name().toString(), false, Thread.currentThread().getContextClassLoader());
returnTypeDescriptor = DescriptorUtils.typeToString(methodInfo.returnType());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
@Override
public void visitEnd() {
super.visitEnd();
addGetter(this.cv);
if (setter != null) {
addSetter(this.cv);
}
}
private void addGetter(ClassVisitor classWriter) {
String methodName = getVirtualGetterName(false, name);
MethodVisitor mv;
mv = classWriter.visitMethod(ACC_PUBLIC, methodName, "()" + returnTypeDescriptor,
null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(clazz), methodInfo.name(),
"()" + returnTypeDescriptor, false);
mv.visitInsn(Type.getType(returnTypeDescriptor).getOpcode(IRETURN));
mv.visitMaxs(0, 0);
}
private void addSetter(ClassVisitor classWriter) {
if (setter == null) {
return;
}
String methodName = getVirtualSetterName(false, name);
MethodVisitor mv;
mv = classWriter.visitMethod(ACC_PUBLIC, methodName, setter.getDescriptor(),
null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(clazz), setter.getName(),
setter.getDescriptor(), false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
}
}
}