@@ -170,81 +170,42 @@ def factory (cls, *args):
170170 return resultClass
171171
172172
173+ _CUSTOM_JAVA_SUBCLASS_BACKSTOPS = {}
174+
175+
173176@builtin
174177def build_new_style_java_class (module , ns , name , base ):
175178 import polyglot
179+ import types
176180
177- # First, generate the Java subclass using the Truffle API. Instances of
178- # this class is what we want to generate.
179181 JavaClass = __graalpython__ .extend (base )
182+ if JavaClass not in _CUSTOM_JAVA_SUBCLASS_BACKSTOPS :
183+ class MroClass :
184+ def __getattr__ (self , name ):
185+ sentinel = object ()
186+ # An attribute access on the Java instance failed, check the
187+ # delegate and then the static Java members
188+ result = getattr (self .this , name , sentinel )
189+ if result is sentinel :
190+ return getattr (self .getClass ().static , name )
191+ else :
192+ return result
193+
194+ def __setattr__ (self , name , value ):
195+ # An attribute access on the Java instance failed, use the delegate
196+ setattr (self .this , name , value )
180197
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 ):
198+ def __delattr__ (self , name ):
199+ # An attribute access on the Java instance failed, use the delegate
200+ delattr (self .this , name )
201+
202+ # This may race, so we allow_method_overwrites, at the only danger to
203+ # insert a few useless classes into the MRO
204+ polyglot .register_interop_type (JavaClass , MroClass , allow_method_overwrites = True )
205+
206+ # A class to make sure that the returned Python class can be used for
207+ # issubclass and isinstance checks with the Java instances
208+ class JavaSubclassMeta (type ):
248209 @property
249210 def __bases__ (self ):
250211 return (JavaClass ,)
@@ -257,16 +218,62 @@ def __subclasscheck__(cls, derived):
257218
258219 def __new__ (mcls , name , bases , namespace ):
259220 if bases :
260- raise NotImplementedError ("Grandchildren of Java classes are not supported" )
221+ new_class = None
222+
223+ class custom_super ():
224+ def __init__ (self , start_type = None , object_or_type = None ):
225+ assert start_type is None and object_or_type is None , "super() calls in Python class inheriting from Java must not receive arguments"
226+ f = sys ._getframe (1 )
227+ self .self = f .f_locals [f .f_code .co_varnames [0 ]]
228+
229+ def __getattribute__ (self , name ):
230+ if name == "__class__" :
231+ return __class__
232+ if name == "self" :
233+ return object .__getattribute__ (self , "self" )
234+ for t in new_class .mro ()[1 :]:
235+ if t == DelegateSuperclass :
236+ break
237+ if name in t .__dict__ :
238+ value = t .__dict__ [name ]
239+ if get := getattr (value , "__get__" , None ):
240+ return get (self .self .this )
241+ return value
242+ return getattr (__graalpython__ .super (self .self ), name )
243+
244+ # Wrap all methods so that the `self` inside is always a Java object, and
245+ # adapt the globals in the functions to provide a custom super() if
246+ # necessary
247+ def self_as_java_wrapper (k , value ):
248+ if type (value ) is not types .FunctionType :
249+ return value
250+ if k in ("__new__" , "__class_getitem__" ):
251+ return value
252+ if "super" in value .__code__ .co_names :
253+ value = types .FunctionType (
254+ value .__code__ ,
255+ value .__globals__ | {"super" : custom_super },
256+ name = value .__name__ ,
257+ argdefs = value .__defaults__ ,
258+ closure = value .__closure__ ,
259+ )
260+ return lambda self , * args , ** kwds : value (self .__this__ , * args , ** kwds )
261+ namespace = {k : self_as_java_wrapper (k , v ) for k , v in namespace .items ()}
262+ new_class = type .__new__ (mcls , name , bases , namespace )
263+ return new_class
261264 return type .__new__ (mcls , name , bases , namespace )
262265
263- class FactoryClass (metaclass = FactoryMeta ):
264- @classmethod
265- def __new__ (cls , * args , ** kwds ):
266- return MroClass .__new__ (* args , ** kwds )
266+ def __getattr__ (self , name ):
267+ return getattr (JavaClass , name )
267268
268- FactoryClass .__name__ = ns ['__qualname__' ].rsplit ("." , 1 )[- 1 ]
269- FactoryClass .__qualname__ = ns ['__qualname__' ]
270- FactoryClass .__module__ = ns ['__module__' ]
269+ # A class that defines the required construction for the Java instances, so
270+ # the Python code can actually override __new__ to affect the construction
271+ # of the Java object
272+ class DelegateSuperclass (metaclass = JavaSubclassMeta ):
273+ def __new__ (cls , * args , ** kwds ):
274+ delegate = object .__new__ (cls )
275+ java_object = polyglot .__new__ (JavaClass , * (args + (delegate ,)))
276+ delegate .__this__ = java_object
277+ return java_object
271278
272- return FactoryClass
279+ return type ( name , ( DelegateSuperclass ,), ns )
0 commit comments