@@ -142,7 +142,17 @@ def import_current_as_named_module_with_delegate(module, module_name, delegate_n
142142
143143
144144@builtin
145- def build_java_class (module , ns , name , base ):
145+ def build_java_class (module , ns , name , base , old_style = True ):
146+ if not old_style :
147+ return build_new_style_java_class (ns , name , base )
148+ import warnings
149+ warnings .warn ("Subclassing Java classes is going to change "
150+ "to a new instance layout that is hopefully "
151+ "more intuitive. Pass the keyword old_style=False "
152+ "to your class definition to try the new style. "
153+ "The new style will become the default in the next "
154+ "release and the old style will be removed soon after." , DeprecationWarning , 1 )
155+
146156 ns ['__super__' ] = None # place where store the original java class when instance is created
147157 ExtenderClass = type ("PythonJavaExtenderClass" , (object , ), ns )
148158 HostAdapter = __graalpython__ .extend (base )
@@ -158,3 +168,105 @@ def factory (cls, *args):
158168
159169 resultClass .__new__ = classmethod (factory )
160170 return resultClass
171+
172+
173+ @builtin
174+ def build_new_style_java_class (module , ns , name , base ):
175+ import polyglot
176+
177+ # First, generate the Java subclass using the Truffle API. Instances of
178+ # this class is what we want to generate.
179+ JavaClass = __graalpython__ .extend (base )
180+
181+ # Second, generate the delegate object class. Code calling from Java will
182+ # end up delegating methods to an instance of this type and the Java object
183+ # will use this delegate instance to manage dynamic attributes.
184+ #
185+ # The __init__ function would not do what the user thinks, so we take it
186+ # out and call it explicitly in the factory below. The `self` passed into
187+ # those Python-defined methods is the delegate instance, but that would be
188+ # confusing for users. So we wrap all methods to get to the Java instance
189+ # and pass that one as `self`.
190+ delegate_namespace = dict (** ns )
191+ delegate_namespace ["__java_init__" ] = delegate_namespace .pop ("__init__" , lambda self , * a , ** kw : None )
192+
193+ def python_to_java_decorator (fun ):
194+ return lambda self , * args , ** kwds : fun (self .__this__ , * args , ** kwds )
195+
196+ for n , v in delegate_namespace .items ():
197+ if type (v ) == type (python_to_java_decorator ):
198+ delegate_namespace [n ] = python_to_java_decorator (v )
199+ DelegateClass = type (f"PythonDelegateClassFor{ base } " , (object ,), delegate_namespace )
200+ DelegateClass .__qualname__ = DelegateClass .__name__
201+
202+ # Third, generate the class used to inject into the MRO of the generated
203+ # Java subclass. Code calling from Python will go through this class for
204+ # lookup.
205+ #
206+ # The `self` passed into those Python-defined methods will be the Java
207+ # instance. We add `__getattr__`, `__setattr__`, and `__delattr__`
208+ # implementations to look to the Python delegate object when the Java-side
209+ # lookup fails. For convenience, we also allow retrieving static fields
210+ # from Java.
211+ mro_namespace = dict (** ns )
212+
213+ def java_getattr (self , name ):
214+ if name == "super" :
215+ return __graalpython__ .super (self )
216+ sentinel = object ()
217+ result = getattr (self .this , name , sentinel )
218+ if result is sentinel :
219+ return getattr (self .getClass ().static , name )
220+ else :
221+ return result
222+
223+ mro_namespace ['__getattr__' ] = java_getattr
224+ mro_namespace ['__setattr__' ] = lambda self , name , value : setattr (self .this , name , value )
225+ mro_namespace ['__delattr__' ] = lambda self , name : delattr (self .this , name )
226+
227+ @classmethod
228+ def factory (cls , * args , ** kwds ):
229+ # create the delegate object
230+ delegate = DelegateClass ()
231+ # create the Java object (remove the class argument and add the delegate instance)
232+ java_object = polyglot .__new__ (JavaClass , * (args [1 :] + (delegate , )))
233+ delegate .__this__ = java_object
234+ # call the __init__ function on the delegate object now that the Java instance is available
235+ delegate .__java_init__ (* args [1 :], ** kwds )
236+ return java_object
237+
238+ mro_namespace ['__constructor__' ] = factory
239+ if '__new__' not in mro_namespace :
240+ mro_namespace ['__new__' ] = classmethod (lambda cls , * args , ** kwds : cls .__constructor__ (* args , ** kwds ))
241+ MroClass = type (f"PythonMROMixinFor{ base } " , (object ,), mro_namespace )
242+ MroClass .__qualname__ = MroClass .__name__
243+ polyglot .register_interop_type (JavaClass , MroClass )
244+
245+ # Finally, generate a factory that implements the factory and type checking
246+ # methods and denies inheriting again
247+ class FactoryMeta (type ):
248+ @property
249+ def __bases__ (self ):
250+ return (JavaClass ,)
251+
252+ def __instancecheck__ (cls , obj ):
253+ return isinstance (obj , JavaClass )
254+
255+ def __subclasscheck__ (cls , derived ):
256+ return cls is derived or issubclass (derived , JavaClass )
257+
258+ def __new__ (mcls , name , bases , namespace ):
259+ if bases :
260+ raise NotImplementedError ("Grandchildren of Java classes are not supported" )
261+ return type .__new__ (mcls , name , bases , namespace )
262+
263+ class FactoryClass (metaclass = FactoryMeta ):
264+ @classmethod
265+ def __new__ (cls , * args , ** kwds ):
266+ return MroClass .__new__ (* args , ** kwds )
267+
268+ FactoryClass .__name__ = ns ['__qualname__' ].rsplit ("." , 1 )[- 1 ]
269+ FactoryClass .__qualname__ = ns ['__qualname__' ]
270+ FactoryClass .__module__ = ns ['__module__' ]
271+
272+ return FactoryClass
0 commit comments