Skip to content

Commit 680309d

Browse files
antonykampSowasvonbot
authored andcommitted
Introduce decorator java_interop_type to register java type
1 parent dcd9b2d commit 680309d

File tree

4 files changed

+245
-2
lines changed

4 files changed

+245
-2
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import sys
2+
import unittest
3+
4+
if sys.implementation.name == "graalpy":
5+
import polyglot
6+
javaClassName = "java.util.ArrayList"
7+
8+
class TestPyStructNumericSequenceTypes(unittest.TestCase):
9+
def tearDown(self):
10+
try:
11+
polyglot.remove_java_interop_type(javaClassName)
12+
except Exception as e:
13+
pass # A test did not register the java class
14+
15+
def test_java_interop_assertions(self):
16+
"""
17+
Test if registering java class and calling it works
18+
"""
19+
import java
20+
21+
class jList(__graalpython__.ForeignType):
22+
def append(self, element):
23+
self.add(element)
24+
25+
polyglot.register_java_interop_type(javaClassName, jList)
26+
l = java.type(javaClassName)()
27+
assert isinstance(l, jList)
28+
29+
l.append(1)
30+
assert len(l) == 1
31+
assert l[0] == 1
32+
33+
def test_java_interop_decorator_assertions(self):
34+
"""
35+
Test if registering with the decorator function works
36+
"""
37+
import java
38+
39+
@polyglot.java_interop_type(javaClassName)
40+
class jList(__graalpython__.ForeignType):
41+
pass
42+
43+
l = java.type(javaClassName)()
44+
assert isinstance(l, jList)
45+
46+
def test_java_interop_overwrite_assertions(self):
47+
"""
48+
Test if overwriting registrations works
49+
"""
50+
import java
51+
52+
class jList(__graalpython__.ForeignType):
53+
pass
54+
55+
class jList2(__graalpython__.ForeignType):
56+
pass
57+
58+
polyglot.register_java_interop_type(javaClassName, jList)
59+
try:
60+
polyglot.register_java_interop_type(javaClassName, jList2)
61+
except Exception as e:
62+
assert True
63+
else:
64+
assert False, "should throw error that class is already registered"
65+
66+
# Overwriting should work now
67+
polyglot.register_java_interop_type(javaClassName, jList2, overwrite=True)
68+
l = java.type(javaClassName)()
69+
assert isinstance(l, jList2)
70+
71+
# Test if overwrite flag works in decorator function too
72+
try:
73+
@polyglot.java_interop_type(javaClassName)
74+
class jList3(__graalpython__.ForeignType):
75+
pass
76+
except Exception as e:
77+
assert True
78+
else: assert False, "should throw an error"
79+
80+
@polyglot.java_interop_type(javaClassName, overwrite=True)
81+
class jList4(__graalpython__.ForeignType):
82+
pass
83+
84+
assert isinstance(l, jList4)
85+
86+
def test_remove_java_interop_assertions(self):
87+
"""
88+
Test if removing registrations work
89+
"""
90+
import java
91+
92+
class jList(__graalpython__.ForeignType):
93+
pass
94+
95+
class jList2(__graalpython__.ForeignType):
96+
pass
97+
98+
try:
99+
polyglot.remove_java_interop_type(javaClassName)
100+
except Exception as e:
101+
assert True
102+
else: assert False, "Should throw an error"
103+
104+
polyglot.register_java_interop_type(javaClassName, jList)
105+
polyglot.remove_java_interop_type(javaClassName)
106+
# register type without overwrite flag
107+
polyglot.register_java_interop_type(javaClassName, jList2)
108+
l = java.type(javaClassName)()
109+
assert isinstance(l, jList2)
110+

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

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@
4242

4343
import static com.oracle.graal.python.nodes.BuiltinNames.J_GET_REGISTERED_INTEROP_BEHAVIOR;
4444
import static com.oracle.graal.python.nodes.BuiltinNames.J_INTEROP_BEHAVIOR;
45+
import static com.oracle.graal.python.nodes.BuiltinNames.J_JAVA_INTEROP_TYPE;
4546
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_INTEROP_BEHAVIOR;
47+
import static com.oracle.graal.python.nodes.BuiltinNames.J_REMOVE_JAVA_INTEROP_TYPE;
4648
import static com.oracle.graal.python.nodes.BuiltinNames.T_REGISTER_INTEROP_BEHAVIOR;
4749
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_JAVA_INTEROP_TYPE;
50+
import static com.oracle.graal.python.nodes.BuiltinNames.T_REGISTER_JAVA_INTEROP_TYPE;
4851
import static com.oracle.graal.python.nodes.ErrorMessages.ARG_MUST_BE_NUMBER;
52+
import static com.oracle.graal.python.nodes.ErrorMessages.INTEROP_TYPE_ALREADY_REGISTERED;
53+
import static com.oracle.graal.python.nodes.ErrorMessages.INTEROP_TYPE_NOT_REGISTERED;
4954
import static com.oracle.graal.python.nodes.ErrorMessages.S_ARG_MUST_BE_S_NOT_P;
5055
import static com.oracle.graal.python.nodes.ErrorMessages.S_CANNOT_HAVE_S;
5156
import static com.oracle.graal.python.nodes.ErrorMessages.S_DOES_NOT_TAKE_VARARGS;
@@ -744,7 +749,7 @@ public static PKeyword[] createKwDefaults(Object receiver) {
744749
}
745750
}
746751

747-
@Builtin(name = J_REGISTER_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 2, takesVarArgs = true, keywordOnlyNames = "overwrite", doc = """
752+
@Builtin(name = J_REGISTER_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 2, takesVarKeywordArgs = true, keywordOnlyNames = {"overwrite" }, doc = """
748753
register_java_interop_type(javaClassName, pythonClass, overwrite=None)
749754
750755
Example registering a custom interop type for the Java ArrayList
@@ -773,7 +778,7 @@ Object register(TruffleString javaClassName, PythonClass pythonClass, Object ove
773778
throw raiseNode.raise(ValueError, S_ARG_MUST_BE_S_NOT_P, "second", "a python class", pythonClass);
774779
}
775780
// Get registry for custom interop types from PythonContext
776-
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
781+
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
777782
String javaClassNameAsString = javaClassName.toString();
778783
// Check if already registered and if overwrite is configured
779784
if (interopTypeRegistry.containsKey(javaClassNameAsString) && !Boolean.TRUE.equals(overwrite)) {
@@ -784,6 +789,131 @@ Object register(TruffleString javaClassName, PythonClass pythonClass, Object ove
784789
}
785790
}
786791

792+
@Builtin(name = J_REMOVE_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, doc = """
793+
remove_java_interop_type(javaClassName)
794+
795+
Remove registration of java interop type. Future registration don't need overwrite flag anymore.
796+
Example removes the custom interop type for the ArrayList
797+
798+
>>> from polyglot import remove_java_interop_type
799+
800+
>>> remove_java_interop_type("java.util.ArrayList")
801+
""")
802+
@GenerateNodeFactory
803+
public abstract static class RemoveJavaInteropTypeNode extends PythonBuiltinNode {
804+
805+
@Specialization
806+
@TruffleBoundary
807+
Object register(TruffleString javaClassName,
808+
@Cached PRaiseNode raiseNode) {
809+
// Get registry for custom interop types from PythonContext
810+
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
811+
String javaClassNameAsString = javaClassName.toString();
812+
// Check if already registered and if overwrite is configured
813+
if (!interopTypeRegistry.containsKey(javaClassNameAsString)) {
814+
throw raiseNode.raise(KeyError, INTEROP_TYPE_NOT_REGISTERED, javaClassNameAsString);
815+
}
816+
interopTypeRegistry.remove(javaClassNameAsString);
817+
return PNone.NONE;
818+
}
819+
}
820+
821+
@Builtin(name = J_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, takesVarKeywordArgs = true, keywordOnlyNames = {"overwrite"}, doc = """
822+
@java_interop_type(javaClassName, overwrite=None)
823+
824+
Example registering a custom interop type for the Java ArrayList
825+
826+
>>> from polyglot import register_java_interop_type
827+
828+
>>> @java_interop_type("java.util.ArrayList")
829+
... class jArrayList(__graalpython__.ForeignType):
830+
... def append(self, element):
831+
... self.add(element)
832+
833+
For subsequent registrations with overwrite behavior use
834+
>>> @java_interop_type("java.util.ArrayList", overwrite=True)
835+
... class jArrayList(__graalpython__.ForeignType):
836+
... pass
837+
""")
838+
@GenerateNodeFactory
839+
public abstract static class JavaInteropTypeDecoratorNode extends PythonBuiltinNode {
840+
static final TruffleString WRAPPER = tsLiteral("wrapper");
841+
public static final TruffleString KW_J_CLASS_NAME = tsLiteral("javaClassName");
842+
843+
public static final TruffleString KW_OVERWRITE = tsLiteral("overwrite");
844+
845+
static class RegisterWrapperRootNode extends PRootNode {
846+
static final TruffleString[] KEYWORDS_HIDDEN_RECEIVER = new TruffleString[]{KW_J_CLASS_NAME, KW_OVERWRITE};
847+
private static final Signature SIGNATURE = new Signature(1, false, -1, false, tsArray("pythonClass"), KEYWORDS_HIDDEN_RECEIVER);
848+
private static final TruffleString MODULE_POLYGLOT = tsLiteral("polyglot");
849+
@Child private ExecutionContext.CalleeContext calleeContext = ExecutionContext.CalleeContext.create();
850+
@Child private PRaiseNode raiseNode = PRaiseNode.create();
851+
@Child private PyObjectGetAttr getAttr = PyObjectGetAttr.create();
852+
@Child private CallVarargsMethodNode callVarargsMethod = CallVarargsMethodNode.create();
853+
854+
protected RegisterWrapperRootNode(TruffleLanguage<?> language) {
855+
super(language);
856+
}
857+
858+
@Override
859+
public Object execute(VirtualFrame frame) {
860+
calleeContext.enter(frame);
861+
Object[] frameArguments = frame.getArguments();
862+
Object pythonClass = PArguments.getArgument(frameArguments, 0);
863+
// note: the hidden kwargs are stored at the end of the positional args
864+
Object javaClassName = PArguments.getArgument(frameArguments, 1);
865+
Object overwrite = PArguments.getArgument(frameArguments, 2);
866+
try {
867+
if (pythonClass instanceof PythonClass klass) {
868+
PythonModule polyglotModule = PythonContext.get(this).lookupBuiltinModule(MODULE_POLYGLOT);
869+
Object register = getAttr.executeCached(frame, polyglotModule, T_REGISTER_JAVA_INTEROP_TYPE);
870+
callVarargsMethod.execute(frame, register, new Object[]{javaClassName, pythonClass}, new PKeyword[]{new PKeyword(KW_OVERWRITE, overwrite)});
871+
return klass;
872+
}
873+
throw raiseNode.raise(ValueError, S_ARG_MUST_BE_S_NOT_P, "first", "a python class", pythonClass);
874+
} finally {
875+
calleeContext.exit(frame, this);
876+
}
877+
}
878+
879+
@Override
880+
public Signature getSignature() {
881+
return SIGNATURE;
882+
}
883+
884+
@Override
885+
public boolean isPythonInternal() {
886+
return true;
887+
}
888+
889+
@Override
890+
public boolean isInternal() {
891+
return true;
892+
}
893+
894+
@Override
895+
public boolean setsUpCalleeContext() {
896+
return true;
897+
}
898+
}
899+
900+
@Specialization
901+
@TruffleBoundary
902+
public Object decorate(TruffleString receiver, Object overwrite,
903+
@Cached PythonObjectFactory factory) {
904+
905+
RootCallTarget callTarget = getContext().getLanguage().createCachedCallTarget(RegisterWrapperRootNode::new, RegisterWrapperRootNode.class);
906+
return factory.createBuiltinFunction(WRAPPER, null, PythonUtils.EMPTY_OBJECT_ARRAY, createKwDefaults(receiver, overwrite), 0, callTarget);
907+
908+
}
909+
910+
public static PKeyword[] createKwDefaults(Object receiver, Object overwrite) {
911+
// the receiver is passed in a hidden keyword argument
912+
// in a pure python decorator this would be passed as a cell
913+
return new PKeyword[]{new PKeyword(KW_J_CLASS_NAME, receiver), new PKeyword(KW_OVERWRITE, overwrite)};
914+
}
915+
}
916+
787917
@Builtin(name = J_REGISTER_INTEROP_BEHAVIOR, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, takesVarKeywordArgs = true, keywordOnlyNames = {"is_boolean", "is_date",
788918
"is_duration", "is_iterator", "is_number", "is_string", "is_time", "is_time_zone", "is_executable", "fits_in_big_integer", "fits_in_byte", "fits_in_double", "fits_in_float",
789919
"fits_in_int", "fits_in_long", "fits_in_short", "as_big_integer", "as_boolean", "as_byte", "as_date", "as_double", "as_duration", "as_float", "as_int", "as_long", "as_short",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ public abstract class BuiltinNames {
337337
public static final String J_JAVA_INTEROP_TYPE = "java_interop_type";
338338
public static final String J_REGISTER_JAVA_INTEROP_TYPE = "register_java_interop_type";
339339
public static final TruffleString T_REGISTER_JAVA_INTEROP_TYPE = tsLiteral(J_REGISTER_JAVA_INTEROP_TYPE);
340+
341+
public static final String J_REMOVE_JAVA_INTEROP_TYPE = "remove_java_interop_type";
340342

341343
public static final String J__CODECS = "_codecs";
342344
public static final TruffleString T__CODECS = tsLiteral(J__CODECS);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ public abstract class ErrorMessages {
390390
public static final TruffleString INTERMEDIATE_OVERFLOW_IN = tsLiteral("intermediate overflow in %s");
391391
public static final TruffleString CANNOT_SET_PROPERTY_ON_INTEROP_EXCEPTION = tsLiteral("Cannot set property on interop exception");
392392
public static final TruffleString INTEROP_TYPE_ALREADY_REGISTERED = tsLiteral("interop type for '%s' already registered");
393+
public static final TruffleString INTEROP_TYPE_NOT_REGISTERED = tsLiteral("interop type for '%s' is not registered");
393394
public static final TruffleString INVALD_OR_UNREADABLE_CLASSPATH = tsLiteral("invalid or unreadable classpath: '%s' - %m");
394395
public static final TruffleString INVALID_ARGS = tsLiteral("%s: invalid arguments");
395396
public static final TruffleString INVALID_BASE_TYPE_OBJ_FOR_CLASS = tsLiteral("Invalid base type object for class %s (base type was '%p' object).");

0 commit comments

Comments
 (0)