Skip to content

Commit e8cc701

Browse files
committed
New style subclassing from Java
1 parent 7760b1e commit e8cc701

File tree

2 files changed

+118
-6
lines changed

2 files changed

+118
-6
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,22 +2434,22 @@ public abstract static class BuildClassNode extends PythonVarargsBuiltinNode {
24342434

24352435
// No BoundaryCallContext: calls only internal well-behaved Python code
24362436
@TruffleBoundary
2437-
private static Object buildJavaClass(Object namespace, TruffleString name, Object base) {
2437+
private static Object buildJavaClass(Object namespace, TruffleString name, Object base, PKeyword[] keywords) {
24382438
// uncached PythonContext get, since this code path is slow in any case
24392439
Object module = PythonContext.get(null).lookupBuiltinModule(T___GRAALPYTHON__);
24402440
Object buildFunction = PyObjectLookupAttr.executeUncached(module, T_BUILD_JAVA_CLASS);
2441-
return CallNode.executeUncached(buildFunction, namespace, name, base);
2441+
return CallNode.executeUncached(buildFunction, new Object[]{namespace, name, base}, keywords);
24422442
}
24432443

24442444
@InliningCutoff
24452445
private static Object buildJavaClass(VirtualFrame frame, Node inliningTarget, PythonLanguage language, PFunction function, Object[] arguments,
2446-
CallDispatchers.FunctionCachedInvokeNode invokeBody,
2446+
PKeyword[] keywords, CallDispatchers.FunctionCachedInvokeNode invokeBody,
24472447
TruffleString name) {
24482448
PDict ns = PFactory.createDict(language, new DynamicObjectStorage(language));
24492449
Object[] args = PArguments.create(0);
24502450
PArguments.setSpecialArgument(args, ns);
24512451
invokeBody.execute(frame, inliningTarget, function, args);
2452-
return buildJavaClass(ns, name, arguments[1]);
2452+
return buildJavaClass(ns, name, arguments[1], keywords);
24532453
}
24542454

24552455
@Specialization
@@ -2492,7 +2492,7 @@ protected Object doItNonFunction(VirtualFrame frame, Object function, Object[] a
24922492

24932493
if (arguments.length == 2 && env.isHostObject(arguments[1]) && env.asHostObject(arguments[1]) instanceof Class<?>) {
24942494
// we want to subclass a Java class
2495-
return buildJavaClass(frame, inliningTarget, language, (PFunction) function, arguments, invokeBody, name);
2495+
return buildJavaClass(frame, inliningTarget, language, (PFunction) function, arguments, keywords, invokeBody, name);
24962496
}
24972497

24982498
class InitializeBuildClass {

graalpython/lib-graalpython/__graalpython__.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)