2323import ai .timefold .jpyinterpreter .implementors .JavaEqualsImplementor ;
2424import ai .timefold .jpyinterpreter .implementors .JavaHashCodeImplementor ;
2525import ai .timefold .jpyinterpreter .implementors .JavaInterfaceImplementor ;
26+ import ai .timefold .jpyinterpreter .implementors .PythonConstantsImplementor ;
2627import ai .timefold .jpyinterpreter .opcodes .AbstractOpcode ;
2728import ai .timefold .jpyinterpreter .opcodes .Opcode ;
2829import ai .timefold .jpyinterpreter .opcodes .SelfOpcodeWithoutSource ;
@@ -205,14 +206,14 @@ public static PythonLikeType translatePythonClass(PythonCompiledClass pythonComp
205206 attributeNameToTypeMap .put (attributeName , type );
206207 FieldVisitor fieldVisitor ;
207208 String javaFieldTypeDescriptor ;
209+ String signature = null ;
208210 boolean isJavaType ;
209211 if (type .getJavaTypeInternalName ().equals (Type .getInternalName (JavaObjectWrapper .class ))) {
210212 javaFieldTypeDescriptor = Type .getDescriptor (type .getJavaObjectWrapperType ());
211213 fieldVisitor = classWriter .visitField (Modifier .PUBLIC , getJavaFieldName (attributeName ), javaFieldTypeDescriptor ,
212214 null , null );
213215 isJavaType = true ;
214216 } else {
215- String signature = null ;
216217 if (typeHint .genericArgs () != null ) {
217218 var signatureWriter = new SignatureWriter ();
218219 visitSignature (typeHint , signatureWriter );
@@ -223,10 +224,12 @@ public static PythonLikeType translatePythonClass(PythonCompiledClass pythonComp
223224 signature , null );
224225 isJavaType = false ;
225226 }
226- for (var annotation : typeHint .annotationList ()) {
227- annotation .addAnnotationTo (fieldVisitor );
228- }
229227 fieldVisitor .visitEnd ();
228+ createJavaGetterSetter (classWriter , preparedClassInfo ,
229+ attributeName ,
230+ Type .getType (javaFieldTypeDescriptor ),
231+ signature ,
232+ typeHint );
230233 FieldDescriptor fieldDescriptor =
231234 new FieldDescriptor (attributeName , getJavaFieldName (attributeName ), internalClassName ,
232235 javaFieldTypeDescriptor , type , true , isJavaType );
@@ -761,6 +764,85 @@ private static PythonLikeFunction createConstructor(String classInternalName,
761764 }
762765 }
763766
767+ private static void createJavaGetterSetter (ClassWriter classWriter ,
768+ PreparedClassInfo preparedClassInfo ,
769+ String attributeName , Type attributeType ,
770+ String signature ,
771+ TypeHint typeHint ) {
772+ createJavaGetter (classWriter , preparedClassInfo , attributeName , attributeType , signature , typeHint );
773+ createJavaSetter (classWriter , preparedClassInfo , attributeName , attributeType , signature , typeHint );
774+ }
775+
776+ private static void createJavaGetter (ClassWriter classWriter , PreparedClassInfo preparedClassInfo , String attributeName ,
777+ Type attributeType , String signature , TypeHint typeHint ) {
778+ var getterName = "get" + attributeName .substring (0 , 1 ).toUpperCase () + attributeName .substring (1 );
779+ if (signature != null ) {
780+ signature = "()" + signature ;
781+ }
782+ var getterVisitor = classWriter .visitMethod (Modifier .PUBLIC , getterName , Type .getMethodDescriptor (attributeType ),
783+ signature , null );
784+ var maxStack = 1 ;
785+
786+ for (var annotation : typeHint .annotationList ()) {
787+ annotation .addAnnotationTo (getterVisitor );
788+ }
789+
790+ getterVisitor .visitCode ();
791+ getterVisitor .visitVarInsn (Opcodes .ALOAD , 0 );
792+ getterVisitor .visitFieldInsn (Opcodes .GETFIELD , preparedClassInfo .classInternalName ,
793+ attributeName , attributeType .getDescriptor ());
794+ if (typeHint .type ().isInstance (PythonNone .INSTANCE )) {
795+ maxStack = 3 ;
796+ getterVisitor .visitInsn (Opcodes .DUP );
797+ PythonConstantsImplementor .loadNone (getterVisitor );
798+ Label returnLabel = new Label ();
799+ getterVisitor .visitJumpInsn (Opcodes .IF_ACMPNE , returnLabel );
800+ // field is None, so we want Java to see it as null
801+ getterVisitor .visitInsn (Opcodes .POP );
802+ getterVisitor .visitInsn (Opcodes .ACONST_NULL );
803+ getterVisitor .visitLabel (returnLabel );
804+ // If branch is taken, stack is field
805+ // If branch is not taken, stack is null
806+ }
807+ getterVisitor .visitInsn (Opcodes .ARETURN );
808+ getterVisitor .visitMaxs (maxStack , 0 );
809+ getterVisitor .visitEnd ();
810+ }
811+
812+ private static void createJavaSetter (ClassWriter classWriter , PreparedClassInfo preparedClassInfo , String attributeName ,
813+ Type attributeType , String signature , TypeHint typeHint ) {
814+ var setterName = "set" + attributeName .substring (0 , 1 ).toUpperCase () + attributeName .substring (1 );
815+ if (signature != null ) {
816+ signature = "(" + signature + ")V" ;
817+ }
818+ var setterVisitor = classWriter .visitMethod (Modifier .PUBLIC , setterName , Type .getMethodDescriptor (Type .VOID_TYPE ,
819+ attributeType ),
820+ signature , null );
821+ var maxStack = 2 ;
822+ setterVisitor .visitCode ();
823+ setterVisitor .visitVarInsn (Opcodes .ALOAD , 0 );
824+ setterVisitor .visitVarInsn (Opcodes .ALOAD , 1 );
825+ if (typeHint .type ().isInstance (PythonNone .INSTANCE )) {
826+ maxStack = 4 ;
827+ // We want to replace null with None
828+ setterVisitor .visitInsn (Opcodes .DUP );
829+ setterVisitor .visitInsn (Opcodes .ACONST_NULL );
830+ Label setFieldLabel = new Label ();
831+ setterVisitor .visitJumpInsn (Opcodes .IF_ACMPNE , setFieldLabel );
832+ // set value is null, so we want Python to see it as None
833+ setterVisitor .visitInsn (Opcodes .POP );
834+ PythonConstantsImplementor .loadNone (setterVisitor );
835+ setterVisitor .visitLabel (setFieldLabel );
836+ // If branch is taken, stack is (non-null instance)
837+ // If branch is not taken, stack is None
838+ }
839+ setterVisitor .visitFieldInsn (Opcodes .PUTFIELD , preparedClassInfo .classInternalName ,
840+ attributeName , attributeType .getDescriptor ());
841+ setterVisitor .visitInsn (Opcodes .RETURN );
842+ setterVisitor .visitMaxs (maxStack , 0 );
843+ setterVisitor .visitEnd ();
844+ }
845+
764846 private static void addAnnotationsToMethod (PythonCompiledFunction function , MethodVisitor methodVisitor ) {
765847 var returnTypeHint = function .typeAnnotations .get ("return" );
766848 if (returnTypeHint != null ) {
@@ -956,15 +1038,9 @@ public static void createGetAttribute(ClassWriter classWriter, String classInter
9561038 methodVisitor .visitVarInsn (Opcodes .ALOAD , 0 );
9571039 var type = fieldToType .get (field );
9581040 if (type .getJavaTypeInternalName ().equals (Type .getInternalName (JavaObjectWrapper .class ))) {
959- Class <?> fieldType = type .getJavaObjectWrapperType ();
9601041 methodVisitor .visitFieldInsn (Opcodes .GETFIELD , classInternalName , getJavaFieldName (field ),
961- Type .getDescriptor (fieldType ));
962- methodVisitor .visitTypeInsn (Opcodes .NEW , Type .getInternalName (JavaObjectWrapper .class ));
963- methodVisitor .visitInsn (Opcodes .DUP_X1 );
964- methodVisitor .visitInsn (Opcodes .DUP_X1 );
965- methodVisitor .visitInsn (Opcodes .POP );
966- methodVisitor .visitMethodInsn (Opcodes .INVOKESPECIAL , Type .getInternalName (JavaObjectWrapper .class ),
967- "<init>" , Type .getMethodDescriptor (Type .VOID_TYPE , Type .getType (Object .class )), false );
1042+ Type .getDescriptor (type .getJavaObjectWrapperType ()));
1043+ getWrappedJavaObject (methodVisitor );
9681044 } else {
9691045 methodVisitor .visitFieldInsn (Opcodes .GETFIELD , classInternalName , getJavaFieldName (field ),
9701046 'L' + type .getJavaTypeInternalName () + ';' );
@@ -984,6 +1060,15 @@ public static void createGetAttribute(ClassWriter classWriter, String classInter
9841060 methodVisitor .visitEnd ();
9851061 }
9861062
1063+ private static void getWrappedJavaObject (MethodVisitor methodVisitor ) {
1064+ methodVisitor .visitTypeInsn (Opcodes .NEW , Type .getInternalName (JavaObjectWrapper .class ));
1065+ methodVisitor .visitInsn (Opcodes .DUP_X1 );
1066+ methodVisitor .visitInsn (Opcodes .DUP_X1 );
1067+ methodVisitor .visitInsn (Opcodes .POP );
1068+ methodVisitor .visitMethodInsn (Opcodes .INVOKESPECIAL , Type .getInternalName (JavaObjectWrapper .class ),
1069+ "<init>" , Type .getMethodDescriptor (Type .VOID_TYPE , Type .getType (Object .class )), false );
1070+ }
1071+
9871072 public static void createSetAttribute (ClassWriter classWriter , String classInternalName , String superInternalName ,
9881073 Collection <String > instanceAttributes ,
9891074 Map <String , PythonLikeType > fieldToType ) {
@@ -1008,10 +1093,7 @@ public static void createSetAttribute(ClassWriter classWriter, String classInter
10081093 String typeDescriptor = type .getJavaTypeDescriptor ();
10091094 if (type .getJavaTypeInternalName ().equals (Type .getInternalName (JavaObjectWrapper .class ))) {
10101095 // Need to unwrap the object
1011- methodVisitor .visitTypeInsn (Opcodes .CHECKCAST , Type .getInternalName (JavaObjectWrapper .class ));
1012- methodVisitor .visitMethodInsn (Opcodes .INVOKEVIRTUAL , Type .getInternalName (JavaObjectWrapper .class ),
1013- "getWrappedObject" , Type .getMethodDescriptor (Type .getType (Object .class )), false );
1014- methodVisitor .visitTypeInsn (Opcodes .CHECKCAST , Type .getType (type .getJavaObjectWrapperType ()).getInternalName ());
1096+ getUnwrappedJavaObject (methodVisitor , type );
10151097 typeDescriptor = Type .getDescriptor (type .getJavaObjectWrapperType ());
10161098 } else {
10171099 methodVisitor .visitTypeInsn (Opcodes .CHECKCAST , type .getJavaTypeInternalName ());
@@ -1035,6 +1117,13 @@ public static void createSetAttribute(ClassWriter classWriter, String classInter
10351117 methodVisitor .visitEnd ();
10361118 }
10371119
1120+ private static void getUnwrappedJavaObject (MethodVisitor methodVisitor , PythonLikeType type ) {
1121+ methodVisitor .visitTypeInsn (Opcodes .CHECKCAST , Type .getInternalName (JavaObjectWrapper .class ));
1122+ methodVisitor .visitMethodInsn (Opcodes .INVOKEVIRTUAL , Type .getInternalName (JavaObjectWrapper .class ),
1123+ "getWrappedObject" , Type .getMethodDescriptor (Type .getType (Object .class )), false );
1124+ methodVisitor .visitTypeInsn (Opcodes .CHECKCAST , Type .getType (type .getJavaObjectWrapperType ()).getInternalName ());
1125+ }
1126+
10381127 public static void createDeleteAttribute (ClassWriter classWriter , String classInternalName , String superInternalName ,
10391128 Collection <String > instanceAttributes ,
10401129 Map <String , PythonLikeType > fieldToType ) {
0 commit comments