Skip to content

Commit e6d254d

Browse files
committed
[GR-26694] Allow subclassing of Java classes
1 parent 4f22561 commit e6d254d

File tree

6 files changed

+185
-0
lines changed

6 files changed

+185
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
This changelog summarizes major changes between GraalVM versions of the Python
44
language runtime. The main focus is on user-observable behavior of the engine.
55

6+
## Version 21.1.0
7+
8+
* Added subclassing of Java classes in JVM mode
9+
610
## Version 21.0.0
711

812
* Implement name mangling for private attributes

graalpython/com.oracle.graal.python.test/src/tests/test_interop.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,99 @@ def test_isinstance02():
516516
assert isinstance(h, HashMap)
517517
assert isinstance(h, Map)
518518

519+
def test_is_type():
520+
import java
521+
from java.util.logging import Handler
522+
from java.util import Set
523+
from java.util.logging import LogRecord
524+
from java.util.logging import Level
525+
526+
assert java.is_type(Handler)
527+
assert java.is_type(LogRecord)
528+
assert java.is_type(Set)
529+
assert java.is_type(Level)
530+
531+
lr = LogRecord(Level.ALL, "message")
532+
assert not java.is_type(lr)
533+
assert not java.is_type(Level.ALL)
534+
assert not java.is_type("ahoj")
535+
536+
def test_extend_java_class_01():
537+
from java.util.logging import Handler
538+
from java.util.logging import LogRecord
539+
from java.util.logging import Level
540+
541+
lr = LogRecord(Level.ALL, "The first message")
542+
543+
# extender object
544+
class MyHandler (Handler):
545+
"This is MyHandler doc"
546+
counter = 0;
547+
def isLoggable(self, logrecord):
548+
self.counter = self.counter + 1
549+
return self.__super__.isLoggable(logrecord)
550+
def sayHello(self):
551+
return 'Hello'
552+
553+
h = MyHandler()
554+
555+
# accessing extender object via this property
556+
assert hasattr(h, 'this')
557+
assert hasattr(h.this, 'sayHello')
558+
assert hasattr(h.this, 'counter')
559+
assert hasattr(h.this, 'isLoggable')
560+
561+
#accessing java methods or methods from extender object directly
562+
assert hasattr(h, 'close')
563+
assert hasattr(h, 'flush')
564+
assert hasattr(h, 'getEncoding')
565+
assert hasattr(h, 'setEncoding')
566+
567+
568+
assert h.this.counter == 0
569+
assert h.isLoggable(lr)
570+
assert h.this.counter == 1
571+
assert h.isLoggable(lr)
572+
assert h.isLoggable(lr)
573+
assert h.this.counter == 3
574+
575+
assert 'Hello' == h.this.sayHello()
576+
577+
h2 = MyHandler()
578+
assert h2.this.counter == 0
579+
assert h2.isLoggable(lr)
580+
assert h2.this.counter == 1
581+
assert h.this.counter == 3
582+
583+
def test_extend_java_class_02():
584+
from java.math import BigDecimal
585+
try:
586+
class MyDecimal(BigDecimal):
587+
pass
588+
except TypeError:
589+
assert True
590+
else:
591+
assert False
592+
593+
def test_extend_java_class_03():
594+
#test of java constructor
595+
from java.util.logging import LogRecord
596+
from java.util.logging import Level
597+
598+
class MyLogRecord(LogRecord):
599+
def getLevel(self):
600+
if self.__super__.getLevel() == Level.FINEST:
601+
self.__super__.setLevel(Level.WARNING)
602+
return self.__super__.getLevel()
603+
604+
message = "log message"
605+
my_lr1 = MyLogRecord(Level.WARNING, message)
606+
assert my_lr1.getLevel() == Level.WARNING
607+
assert my_lr1.getMessage() == message
608+
609+
my_lr2 = MyLogRecord(Level.FINEST, message)
610+
assert my_lr2.getLevel() == Level.WARNING
611+
519612
def test_foreign_slice_setting():
520613
import java
521614
il = java.type("int[]")(20)

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
import com.oracle.graal.python.runtime.PythonContext;
9696
import com.oracle.graal.python.runtime.PythonCore;
9797
import com.oracle.graal.python.runtime.PythonOptions;
98+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.SystemError;
99+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
98100
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
99101
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
100102
import com.oracle.graal.python.util.PythonUtils;
@@ -103,6 +105,7 @@
103105
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
104106
import com.oracle.truffle.api.RootCallTarget;
105107
import com.oracle.truffle.api.TruffleFile;
108+
import com.oracle.truffle.api.TruffleLanguage;
106109
import com.oracle.truffle.api.TruffleLanguage.Env;
107110
import com.oracle.truffle.api.TruffleLogger;
108111
import com.oracle.truffle.api.TruffleOptions;
@@ -116,6 +119,9 @@
116119
import com.oracle.truffle.api.dsl.Specialization;
117120
import com.oracle.truffle.api.dsl.TypeSystemReference;
118121
import com.oracle.truffle.api.frame.VirtualFrame;
122+
import com.oracle.truffle.api.interop.InteropLibrary;
123+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
124+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
119125
import com.oracle.truffle.api.library.CachedLibrary;
120126
import com.oracle.truffle.api.nodes.LanguageInfo;
121127
import com.oracle.truffle.api.nodes.Node;
@@ -624,4 +630,53 @@ private void validate(HashingStorage dictStorage) {
624630
}
625631
}
626632
}
633+
634+
@Builtin(name = "extend", minNumOfPositionalArgs = 1, doc = "Extends Java class and return HostAdapterCLass")
635+
@GenerateNodeFactory
636+
public abstract static class JavaExtendNode extends PythonUnaryBuiltinNode {
637+
@Specialization
638+
Object doIt(Object value) {
639+
if (ImageInfo.inImageBuildtimeCode()) {
640+
CompilerDirectives.transferToInterpreterAndInvalidate();
641+
throw new UnsupportedOperationException(ErrorMessages.CANT_EXTEND_JAVA_CLASS_NOT_JVM);
642+
}
643+
if (ImageInfo.inImageRuntimeCode()) {
644+
CompilerDirectives.transferToInterpreterAndInvalidate();
645+
throw raise(SystemError, ErrorMessages.CANT_EXTEND_JAVA_CLASS_NOT_JVM);
646+
}
647+
648+
Env env = getContext().getEnv();
649+
if (!isType(value, env)) {
650+
throw raise(TypeError, ErrorMessages.CANT_EXTEND_JAVA_CLASS_NOT_TYPE, value);
651+
}
652+
653+
final Class<?>[] types = new Class<?>[1];
654+
types[0] = (Class<?>) env.asHostObject(value);
655+
try {
656+
return env.createHostAdapterClass(types);
657+
} catch (Exception ex) {
658+
throw raise(TypeError, ex.getMessage(), ex);
659+
}
660+
}
661+
662+
protected static boolean isType(Object obj, TruffleLanguage.Env env) {
663+
return env.isHostObject(obj) && env.asHostObject(obj) instanceof Class<?>;
664+
}
665+
666+
}
667+
668+
@Builtin(name = "super", minNumOfPositionalArgs = 1, doc = "Returns HostAdapter instance of the object or None")
669+
@GenerateNodeFactory
670+
public abstract static class JavaSuperNode extends PythonUnaryBuiltinNode {
671+
@Specialization
672+
@TruffleBoundary
673+
Object doIt(Object value) {
674+
try {
675+
return InteropLibrary.getUncached().readMember(value, "super");
676+
} catch (UnsupportedMessageException | UnknownIdentifierException e) {
677+
return PNone.NONE;
678+
}
679+
}
680+
}
681+
627682
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ boolean check(Object object) {
199199
}
200200
}
201201

202+
@Builtin(name = "is_type", minNumOfPositionalArgs = 1)
203+
@GenerateNodeFactory
204+
abstract static class IsTypeNode extends PythonUnaryBuiltinNode {
205+
@Specialization
206+
boolean isType(Object object) {
207+
Env env = getContext().getEnv();
208+
return env.isHostObject(object) && env.asHostObject(object) instanceof Class<?>;
209+
}
210+
}
211+
202212
@Builtin(name = "instanceof", minNumOfPositionalArgs = 2)
203213
@GenerateNodeFactory
204214
abstract static class InstanceOfNode extends PythonBinaryBuiltinNode {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ public abstract class ErrorMessages {
167167
public static final String CANT_CONVERT_TO_STR_EXPLICITELY = "Can't convert '%p' object to str implicitly";
168168
public static final String CANT_COMPARE = "Can't compare %p and %p";
169169
public static final String CANT_DELETE = "can't delete '%s'";
170+
public static final String CANT_EXTEND_JAVA_CLASS_NOT_JVM = "Java Class can be extended only in JVM mode.";
171+
public static final String CANT_EXTEND_JAVA_CLASS_NOT_TYPE = "Function extend needs a Java type as its argument not %p";
170172
public static final String CANT_FIND_MODULE = "can't find module '%s'";
171173
public static final String CANT_MULTIPLY_SEQ_BY_NON_INT = "can't multiply sequence by non-int of type '%p'";
172174
public static final String CANT_PICKLE_FUNC_OBJS = "can't pickle function objects";

graalpython/lib-graalpython/classes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,27 @@ def __build_class__(func, name, *bases, **kwargs):
142142
raise TypeError("__build_class__: func must be a function")
143143
if not isinstance(name, str):
144144
raise TypeError("__build_class__: name is not a string, got '%s'" % type(name))
145+
if len(bases) == 1:
146+
import java
147+
if java.is_type(bases[0]):
148+
ns = {}
149+
func(ns) # fill up namespace with the methods and fields of the class
150+
ns['__super__'] = None # place where store the original java class when instance is created
151+
ExtenderClass = type("PythonJavaExtenderClass", (object, ), ns)
152+
HostAdapter = __graalpython__.extend(bases[0])
153+
resultClass = type(name, (object, ), {})
154+
155+
def factory (cls, *args):
156+
# creates extender object and store the super java class
157+
extenderInstance = ExtenderClass()
158+
args = args[1:] + (extenderInstance, ) # remove the class and add the extender instance object
159+
hostObject = HostAdapter(*args) # create new adapter
160+
extenderInstance.__super__ = __graalpython__.super(hostObject) #set the super java object
161+
return hostObject
162+
163+
resultClass.__new__ = classmethod(factory)
164+
return resultClass
165+
145166
return new_class(name, bases=bases, kwds=kwargs, exec_body=func)
146167

147168

0 commit comments

Comments
 (0)