Skip to content

Commit dcd9b2d

Browse files
OlliwehrSowasvonbot
authored andcommitted
Introduce registration of custom python classes associated to Java Classes
Currently, this enables the registration for custom python classes that should be associated with certain Java classes. The registry is part of a PythonContext and is currently indexed by String keys, which are the java class names for which python classes have been registered. Registration takes place in a respective PolyglotModuleBuiltin and the lookup happens in the GetClassNode#getForeign implementation. Usage in python: `register_java_interop_type("{java_class_name}", pythonClass)` In the future, also an annotation-style registration way should be implemented.
1 parent f159944 commit dcd9b2d

File tree

5 files changed

+83
-2
lines changed

5 files changed

+83
-2
lines changed

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import static com.oracle.graal.python.nodes.BuiltinNames.J_INTEROP_BEHAVIOR;
4545
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_INTEROP_BEHAVIOR;
4646
import static com.oracle.graal.python.nodes.BuiltinNames.T_REGISTER_INTEROP_BEHAVIOR;
47+
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_JAVA_INTEROP_TYPE;
4748
import static com.oracle.graal.python.nodes.ErrorMessages.ARG_MUST_BE_NUMBER;
4849
import static com.oracle.graal.python.nodes.ErrorMessages.S_ARG_MUST_BE_S_NOT_P;
4950
import static com.oracle.graal.python.nodes.ErrorMessages.S_CANNOT_HAVE_S;
@@ -61,6 +62,7 @@
6162
import static com.oracle.graal.python.nodes.StringLiterals.T_READABLE;
6263
import static com.oracle.graal.python.nodes.StringLiterals.T_WRITABLE;
6364
import static com.oracle.graal.python.nodes.truffle.TruffleStringMigrationHelpers.isJavaString;
65+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.NotImplementedError;
6466
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OSError;
6567
import static com.oracle.graal.python.runtime.exception.PythonErrorType.RuntimeError;
6668
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
@@ -742,6 +744,46 @@ public static PKeyword[] createKwDefaults(Object receiver) {
742744
}
743745
}
744746

747+
@Builtin(name = J_REGISTER_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 2, takesVarArgs = true, keywordOnlyNames = "overwrite", doc = """
748+
register_java_interop_type(javaClassName, pythonClass, overwrite=None)
749+
750+
Example registering a custom interop type for the Java ArrayList
751+
752+
>>> from polyglot import register_java_interop_type
753+
754+
>>> class jArrayList(__graalpython__.ForeignType):
755+
... def append(self, element):
756+
... self.add(element)
757+
758+
>>> register_java_interop_type("java.util.ArrayList", jArrayList)
759+
760+
For subsequent registrations with overwrite behavior use
761+
>>> register_java_interop_type("java.util.ArrayList", newJArrayList, overwrite=True)
762+
""")
763+
@GenerateNodeFactory
764+
public abstract static class RegisterJavaInteropTypeNode extends PythonBuiltinNode {
765+
766+
@Specialization
767+
@TruffleBoundary
768+
Object register(TruffleString javaClassName, PythonClass pythonClass, Object overwrite,
769+
@Bind("this") Node inliningTarget,
770+
@Cached TypeNodes.IsTypeNode isClassTypeNode,
771+
@Cached PRaiseNode raiseNode) {
772+
if (!isClassTypeNode.execute(inliningTarget, pythonClass)) {
773+
throw raiseNode.raise(ValueError, S_ARG_MUST_BE_S_NOT_P, "second", "a python class", pythonClass);
774+
}
775+
// Get registry for custom interop types from PythonContext
776+
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
777+
String javaClassNameAsString = javaClassName.toString();
778+
// Check if already registered and if overwrite is configured
779+
if (interopTypeRegistry.containsKey(javaClassNameAsString) && !Boolean.TRUE.equals(overwrite)) {
780+
throw raiseNode.raise(KeyError, INTEROP_TYPE_ALREADY_REGISTERED, javaClassNameAsString);
781+
}
782+
interopTypeRegistry.put(javaClassNameAsString, pythonClass);
783+
return PNone.NONE;
784+
}
785+
}
786+
745787
@Builtin(name = J_REGISTER_INTEROP_BEHAVIOR, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, takesVarKeywordArgs = true, keywordOnlyNames = {"is_boolean", "is_date",
746788
"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",
747789
"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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@ public abstract class BuiltinNames {
334334

335335
public static final String J___GRAALPYTHON_INTEROP_BEHAVIOR__ = "__graalpython_interop_behavior__";
336336

337+
public static final String J_JAVA_INTEROP_TYPE = "java_interop_type";
338+
public static final String J_REGISTER_JAVA_INTEROP_TYPE = "register_java_interop_type";
339+
public static final TruffleString T_REGISTER_JAVA_INTEROP_TYPE = tsLiteral(J_REGISTER_JAVA_INTEROP_TYPE);
340+
337341
public static final String J__CODECS = "_codecs";
338342
public static final TruffleString T__CODECS = tsLiteral(J__CODECS);
339343

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
@@ -389,6 +389,7 @@ public abstract class ErrorMessages {
389389
public static final TruffleString INTEGER_REQUIRED_GOT = tsLiteral("an integer is required (got type %p)");
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");
392+
public static final TruffleString INTEROP_TYPE_ALREADY_REGISTERED = tsLiteral("interop type for '%s' already registered");
392393
public static final TruffleString INVALD_OR_UNREADABLE_CLASSPATH = tsLiteral("invalid or unreadable classpath: '%s' - %m");
393394
public static final TruffleString INVALID_ARGS = tsLiteral("%s: invalid arguments");
394395
public static final TruffleString INVALID_BASE_TYPE_OBJ_FOR_CLASS = tsLiteral("Invalid base type object for class %s (base type was '%p' object).");

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetClassNode.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@
5353
import com.oracle.graal.python.builtins.objects.function.PFunction;
5454
import com.oracle.graal.python.builtins.objects.object.PythonObject;
5555
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
56+
import com.oracle.graal.python.builtins.objects.type.PythonClass;
5657
import com.oracle.graal.python.nodes.HiddenAttr;
5758
import com.oracle.graal.python.nodes.PGuards;
5859
import com.oracle.graal.python.nodes.PNodeWithContext;
5960
import com.oracle.graal.python.nodes.truffle.PythonTypes;
61+
import com.oracle.graal.python.runtime.PythonContext;
6062
import com.oracle.graal.python.runtime.exception.PException;
6163
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
6264
import com.oracle.truffle.api.dsl.Bind;
@@ -70,10 +72,15 @@
7072
import com.oracle.truffle.api.dsl.Specialization;
7173
import com.oracle.truffle.api.dsl.TypeSystemReference;
7274
import com.oracle.truffle.api.exception.AbstractTruffleException;
75+
import com.oracle.truffle.api.interop.InteropLibrary;
76+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
77+
import com.oracle.truffle.api.library.CachedLibrary;
7378
import com.oracle.truffle.api.nodes.Node;
7479
import com.oracle.truffle.api.object.Shape;
7580
import com.oracle.truffle.api.strings.TruffleString;
7681

82+
import java.util.Map;
83+
7784
@TypeSystemReference(PythonTypes.class)
7885
@ImportStatic({PGuards.class})
7986
@GenerateUncached
@@ -259,7 +266,23 @@ static Object getTruffleException(@SuppressWarnings("unused") AbstractTruffleExc
259266
}
260267

261268
@Fallback
262-
static Object getForeign(@SuppressWarnings("unused") Object object) {
263-
return PythonBuiltinClassType.ForeignObject;
269+
static Object getForeign(@SuppressWarnings("unused") Object object,
270+
@CachedLibrary(limit = "3") InteropLibrary interopLib,
271+
@Bind("this") Node inliningTarget) {
272+
try {
273+
// Retrieve the meta object of the requested object in order to get to the class
274+
Object metaObject = interopLib.getMetaObject(object);
275+
// Get class name from meta object
276+
String truffleClassName = (String) interopLib.getMetaQualifiedName(metaObject);
277+
// Get Registry for custom python types from PythonContext
278+
Map<Object, PythonClass> registry = PythonContext.get(inliningTarget).getInteropTypeRegistry();
279+
if (registry.containsKey(truffleClassName)) {
280+
// If a custom python class was registered, take that one.
281+
return registry.get(truffleClassName);
282+
}
283+
return PythonBuiltinClassType.ForeignObject;
284+
} catch (UnsupportedMessageException e) {
285+
return PythonBuiltinClassType.ForeignObject;
286+
}
264287
}
265288
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import java.util.concurrent.locks.ReentrantLock;
101101
import java.util.logging.Level;
102102

103+
import com.oracle.graal.python.builtins.objects.type.PythonClass;
103104
import org.graalvm.nativeimage.ImageInfo;
104105
import org.graalvm.options.OptionKey;
105106

@@ -849,6 +850,16 @@ public Thread getOwner() {
849850

850851
public RootCallTarget signatureContainer;
851852

853+
private Map<Object, PythonClass> interopTypeRegistry;
854+
855+
public Map<Object, PythonClass> getInteropTypeRegistry() {
856+
if (interopTypeRegistry == null) {
857+
// lazy init when needed
858+
interopTypeRegistry = new WeakHashMap<>();
859+
}
860+
return interopTypeRegistry;
861+
}
862+
852863
public TruffleString getPyPackageContext() {
853864
return pyPackageContext;
854865
}

0 commit comments

Comments
 (0)