Skip to content

Commit 629b612

Browse files
committed
[GR-26694] Allow subclassing of Java classes
PullRequest: graalpython/1656
2 parents 634ea55 + e6d254d commit 629b612

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
@@ -522,6 +522,99 @@ def test_isinstance02():
522522
assert isinstance(h, HashMap)
523523
assert isinstance(h, Map)
524524

525+
def test_is_type():
526+
import java
527+
from java.util.logging import Handler
528+
from java.util import Set
529+
from java.util.logging import LogRecord
530+
from java.util.logging import Level
531+
532+
assert java.is_type(Handler)
533+
assert java.is_type(LogRecord)
534+
assert java.is_type(Set)
535+
assert java.is_type(Level)
536+
537+
lr = LogRecord(Level.ALL, "message")
538+
assert not java.is_type(lr)
539+
assert not java.is_type(Level.ALL)
540+
assert not java.is_type("ahoj")
541+
542+
def test_extend_java_class_01():
543+
from java.util.logging import Handler
544+
from java.util.logging import LogRecord
545+
from java.util.logging import Level
546+
547+
lr = LogRecord(Level.ALL, "The first message")
548+
549+
# extender object
550+
class MyHandler (Handler):
551+
"This is MyHandler doc"
552+
counter = 0;
553+
def isLoggable(self, logrecord):
554+
self.counter = self.counter + 1
555+
return self.__super__.isLoggable(logrecord)
556+
def sayHello(self):
557+
return 'Hello'
558+
559+
h = MyHandler()
560+
561+
# accessing extender object via this property
562+
assert hasattr(h, 'this')
563+
assert hasattr(h.this, 'sayHello')
564+
assert hasattr(h.this, 'counter')
565+
assert hasattr(h.this, 'isLoggable')
566+
567+
#accessing java methods or methods from extender object directly
568+
assert hasattr(h, 'close')
569+
assert hasattr(h, 'flush')
570+
assert hasattr(h, 'getEncoding')
571+
assert hasattr(h, 'setEncoding')
572+
573+
574+
assert h.this.counter == 0
575+
assert h.isLoggable(lr)
576+
assert h.this.counter == 1
577+
assert h.isLoggable(lr)
578+
assert h.isLoggable(lr)
579+
assert h.this.counter == 3
580+
581+
assert 'Hello' == h.this.sayHello()
582+
583+
h2 = MyHandler()
584+
assert h2.this.counter == 0
585+
assert h2.isLoggable(lr)
586+
assert h2.this.counter == 1
587+
assert h.this.counter == 3
588+
589+
def test_extend_java_class_02():
590+
from java.math import BigDecimal
591+
try:
592+
class MyDecimal(BigDecimal):
593+
pass
594+
except TypeError:
595+
assert True
596+
else:
597+
assert False
598+
599+
def test_extend_java_class_03():
600+
#test of java constructor
601+
from java.util.logging import LogRecord
602+
from java.util.logging import Level
603+
604+
class MyLogRecord(LogRecord):
605+
def getLevel(self):
606+
if self.__super__.getLevel() == Level.FINEST:
607+
self.__super__.setLevel(Level.WARNING)
608+
return self.__super__.getLevel()
609+
610+
message = "log message"
611+
my_lr1 = MyLogRecord(Level.WARNING, message)
612+
assert my_lr1.getLevel() == Level.WARNING
613+
assert my_lr1.getMessage() == message
614+
615+
my_lr2 = MyLogRecord(Level.FINEST, message)
616+
assert my_lr2.getLevel() == Level.WARNING
617+
525618
def test_foreign_slice_setting():
526619
import java
527620
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
@@ -166,6 +166,8 @@ public abstract class ErrorMessages {
166166
public static final String CANT_CONVERT_TO_STR_EXPLICITELY = "Can't convert '%p' object to str implicitly";
167167
public static final String CANT_COMPARE = "Can't compare %p and %p";
168168
public static final String CANT_DELETE = "can't delete '%s'";
169+
public static final String CANT_EXTEND_JAVA_CLASS_NOT_JVM = "Java Class can be extended only in JVM mode.";
170+
public static final String CANT_EXTEND_JAVA_CLASS_NOT_TYPE = "Function extend needs a Java type as its argument not %p";
169171
public static final String CANT_FIND_MODULE = "can't find module '%s'";
170172
public static final String CANT_MULTIPLY_SEQ_BY_NON_INT = "can't multiply sequence by non-int of type '%p'";
171173
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)