Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python types for polyglot objects #406

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import sys
import unittest

if sys.implementation.name == "graalpy":
import polyglot
javaClassName = "java.util.ArrayList"

class TestPyStructNumericSequenceTypes(unittest.TestCase):
def tearDown(self):
try:
polyglot.remove_java_interop_type(javaClassName)
except Exception as e:
pass # A test did not register the java class

def test_java_interop_assertions(self):
"""
Test if registering java class and calling it works
"""
import java

class jList(__graalpython__.ForeignType):
def append(self, element):
self.add(element)

polyglot.register_java_interop_type(javaClassName, jList)
l = java.type(javaClassName)()
assert isinstance(l, jList)

l.append(1)
assert len(l) == 1
assert l[0] == 1

def test_java_interop_decorator_assertions(self):
"""
Test if registering with the decorator function works
"""
import java

@polyglot.java_interop_type(javaClassName)
class jList(__graalpython__.ForeignType):
pass

l = java.type(javaClassName)()
assert isinstance(l, jList)

def test_java_interop_overwrite_assertions(self):
"""
Test if overwriting registrations works
"""
import java

class jList(__graalpython__.ForeignType):
pass

class jList2(__graalpython__.ForeignType):
pass

polyglot.register_java_interop_type(javaClassName, jList)
try:
polyglot.register_java_interop_type(javaClassName, jList2)
except Exception as e:
assert True
else:
assert False, "should throw error that class is already registered"

# Overwriting should work now
polyglot.register_java_interop_type(javaClassName, jList2, overwrite=True)
l = java.type(javaClassName)()
assert isinstance(l, jList2)

# Test if overwrite flag works in decorator function too
try:
@polyglot.java_interop_type(javaClassName)
class jList3(__graalpython__.ForeignType):
pass
except Exception as e:
assert True
else: assert False, "should throw an error"

@polyglot.java_interop_type(javaClassName, overwrite=True)
class jList4(__graalpython__.ForeignType):
pass

assert isinstance(l, jList4)

def test_remove_java_interop_assertions(self):
"""
Test if removing registrations work
"""
import java

class jList(__graalpython__.ForeignType):
pass

class jList2(__graalpython__.ForeignType):
pass

try:
polyglot.remove_java_interop_type(javaClassName)
except Exception as e:
assert True
else: assert False, "Should throw an error"

polyglot.register_java_interop_type(javaClassName, jList)
polyglot.remove_java_interop_type(javaClassName)
# register type without overwrite flag
polyglot.register_java_interop_type(javaClassName, jList2)
l = java.type(javaClassName)()
assert isinstance(l, jList2)

Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
@ExportLibrary(ReflectionLibrary.class)
public enum PythonBuiltinClassType implements TruffleObject {

ForeignObject(J_FOREIGN, Flags.PRIVATE_DERIVED_WODICT, FOREIGNOBJECT_M_FLAGS, ForeignObjectBuiltins.SLOTS),
ForeignObject(J_FOREIGN, Flags.PRIVATE_BASE_WODICT, FOREIGNOBJECT_M_FLAGS, ForeignObjectBuiltins.SLOTS),
Boolean("bool", J_BUILTINS, Flags.PUBLIC_DERIVED_WODICT, BOOLEAN_M_FLAGS),
PArray("array", "array", ARRAY_M_FLAGS, ArrayBuiltins.SLOTS),
PArrayIterator("arrayiterator", Flags.PRIVATE_DERIVED_WODICT),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@

import static com.oracle.graal.python.nodes.BuiltinNames.J_GET_REGISTERED_INTEROP_BEHAVIOR;
import static com.oracle.graal.python.nodes.BuiltinNames.J_INTEROP_BEHAVIOR;
import static com.oracle.graal.python.nodes.BuiltinNames.J_JAVA_INTEROP_TYPE;
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_INTEROP_BEHAVIOR;
import static com.oracle.graal.python.nodes.BuiltinNames.J_REMOVE_JAVA_INTEROP_TYPE;
import static com.oracle.graal.python.nodes.BuiltinNames.T_REGISTER_INTEROP_BEHAVIOR;
import static com.oracle.graal.python.nodes.BuiltinNames.J_REGISTER_JAVA_INTEROP_TYPE;
import static com.oracle.graal.python.nodes.BuiltinNames.T_REGISTER_JAVA_INTEROP_TYPE;
import static com.oracle.graal.python.nodes.ErrorMessages.ARG_MUST_BE_NUMBER;
import static com.oracle.graal.python.nodes.ErrorMessages.INTEROP_TYPE_ALREADY_REGISTERED;
import static com.oracle.graal.python.nodes.ErrorMessages.INTEROP_TYPE_NOT_REGISTERED;
import static com.oracle.graal.python.nodes.ErrorMessages.S_ARG_MUST_BE_S_NOT_P;
import static com.oracle.graal.python.nodes.ErrorMessages.S_CANNOT_HAVE_S;
import static com.oracle.graal.python.nodes.ErrorMessages.S_DOES_NOT_TAKE_VARARGS;
Expand All @@ -61,6 +67,8 @@
import static com.oracle.graal.python.nodes.StringLiterals.T_READABLE;
import static com.oracle.graal.python.nodes.StringLiterals.T_WRITABLE;
import static com.oracle.graal.python.nodes.truffle.TruffleStringMigrationHelpers.isJavaString;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.KeyError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.NotImplementedError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OSError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.RuntimeError;
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
Expand Down Expand Up @@ -742,6 +750,171 @@ public static PKeyword[] createKwDefaults(Object receiver) {
}
}

@Builtin(name = J_REGISTER_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 2, takesVarKeywordArgs = true, keywordOnlyNames = {"overwrite" }, doc = """
register_java_interop_type(javaClassName, pythonClass, overwrite=None)

Example registering a custom interop type for the Java ArrayList

>>> from polyglot import register_java_interop_type

>>> class jArrayList(__graalpython__.ForeignType):
... def append(self, element):
... self.add(element)

>>> register_java_interop_type("java.util.ArrayList", jArrayList)

For subsequent registrations with overwrite behavior use
>>> register_java_interop_type("java.util.ArrayList", newJArrayList, overwrite=True)
""")
@GenerateNodeFactory
public abstract static class RegisterJavaInteropTypeNode extends PythonBuiltinNode {

@Specialization
@TruffleBoundary
Object register(TruffleString javaClassName, PythonClass pythonClass, Object overwrite,
@Bind("this") Node inliningTarget,
@Cached TypeNodes.IsTypeNode isClassTypeNode,
@Cached PRaiseNode raiseNode) {
if (!isClassTypeNode.execute(inliningTarget, pythonClass)) {
throw raiseNode.raise(ValueError, S_ARG_MUST_BE_S_NOT_P, "second", "a python class", pythonClass);
}
// Get registry for custom interop types from PythonContext
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
String javaClassNameAsString = javaClassName.toString();
// Check if already registered and if overwrite is configured
if (interopTypeRegistry.containsKey(javaClassNameAsString) && !Boolean.TRUE.equals(overwrite)) {
throw raiseNode.raise(KeyError, INTEROP_TYPE_ALREADY_REGISTERED, javaClassNameAsString);
}
interopTypeRegistry.put(javaClassNameAsString, pythonClass);
return PNone.NONE;
}
}

@Builtin(name = J_REMOVE_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, doc = """
remove_java_interop_type(javaClassName)

Remove registration of java interop type. Future registration don't need overwrite flag anymore.
Example removes the custom interop type for the ArrayList

>>> from polyglot import remove_java_interop_type

>>> remove_java_interop_type("java.util.ArrayList")
""")
@GenerateNodeFactory
public abstract static class RemoveJavaInteropTypeNode extends PythonBuiltinNode {

@Specialization
@TruffleBoundary
Object register(TruffleString javaClassName,
@Cached PRaiseNode raiseNode) {
// Get registry for custom interop types from PythonContext
Map<Object, PythonClass> interopTypeRegistry = PythonContext.get(this).getInteropTypeRegistry();
String javaClassNameAsString = javaClassName.toString();
// Check if already registered and if overwrite is configured
if (!interopTypeRegistry.containsKey(javaClassNameAsString)) {
throw raiseNode.raise(KeyError, INTEROP_TYPE_NOT_REGISTERED, javaClassNameAsString);
}
interopTypeRegistry.remove(javaClassNameAsString);
return PNone.NONE;
}
}

@Builtin(name = J_JAVA_INTEROP_TYPE, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, takesVarKeywordArgs = true, keywordOnlyNames = {"overwrite"}, doc = """
@java_interop_type(javaClassName, overwrite=None)

Example registering a custom interop type for the Java ArrayList

>>> from polyglot import register_java_interop_type

>>> @java_interop_type("java.util.ArrayList")
... class jArrayList(__graalpython__.ForeignType):
... def append(self, element):
... self.add(element)

For subsequent registrations with overwrite behavior use
>>> @java_interop_type("java.util.ArrayList", overwrite=True)
... class jArrayList(__graalpython__.ForeignType):
... pass
""")
@GenerateNodeFactory
public abstract static class JavaInteropTypeDecoratorNode extends PythonBuiltinNode {
static final TruffleString WRAPPER = tsLiteral("wrapper");
public static final TruffleString KW_J_CLASS_NAME = tsLiteral("javaClassName");

public static final TruffleString KW_OVERWRITE = tsLiteral("overwrite");

static class RegisterWrapperRootNode extends PRootNode {
static final TruffleString[] KEYWORDS_HIDDEN_RECEIVER = new TruffleString[]{KW_J_CLASS_NAME, KW_OVERWRITE};
private static final Signature SIGNATURE = new Signature(1, false, -1, false, tsArray("pythonClass"), KEYWORDS_HIDDEN_RECEIVER);
private static final TruffleString MODULE_POLYGLOT = tsLiteral("polyglot");
@Child private ExecutionContext.CalleeContext calleeContext = ExecutionContext.CalleeContext.create();
@Child private PRaiseNode raiseNode = PRaiseNode.create();
@Child private PyObjectGetAttr getAttr = PyObjectGetAttr.create();
@Child private CallVarargsMethodNode callVarargsMethod = CallVarargsMethodNode.create();

protected RegisterWrapperRootNode(TruffleLanguage<?> language) {
super(language);
}

@Override
public Object execute(VirtualFrame frame) {
calleeContext.enter(frame);
Object[] frameArguments = frame.getArguments();
Object pythonClass = PArguments.getArgument(frameArguments, 0);
// note: the hidden kwargs are stored at the end of the positional args
Object javaClassName = PArguments.getArgument(frameArguments, 1);
Object overwrite = PArguments.getArgument(frameArguments, 2);
try {
if (pythonClass instanceof PythonClass klass) {
PythonModule polyglotModule = PythonContext.get(this).lookupBuiltinModule(MODULE_POLYGLOT);
Object register = getAttr.executeCached(frame, polyglotModule, T_REGISTER_JAVA_INTEROP_TYPE);
callVarargsMethod.execute(frame, register, new Object[]{javaClassName, pythonClass}, new PKeyword[]{new PKeyword(KW_OVERWRITE, overwrite)});
return klass;
}
throw raiseNode.raise(ValueError, S_ARG_MUST_BE_S_NOT_P, "first", "a python class", pythonClass);
} finally {
calleeContext.exit(frame, this);
}
}

@Override
public Signature getSignature() {
return SIGNATURE;
}

@Override
public boolean isPythonInternal() {
return true;
}

@Override
public boolean isInternal() {
return true;
}

@Override
public boolean setsUpCalleeContext() {
return true;
}
}

@Specialization
@TruffleBoundary
public Object decorate(TruffleString receiver, Object overwrite,
@Cached PythonObjectFactory factory) {

RootCallTarget callTarget = getContext().getLanguage().createCachedCallTarget(RegisterWrapperRootNode::new, RegisterWrapperRootNode.class);
return factory.createBuiltinFunction(WRAPPER, null, PythonUtils.EMPTY_OBJECT_ARRAY, createKwDefaults(receiver, overwrite), 0, callTarget);

}

public static PKeyword[] createKwDefaults(Object receiver, Object overwrite) {
// the receiver is passed in a hidden keyword argument
// in a pure python decorator this would be passed as a cell
return new PKeyword[]{new PKeyword(KW_J_CLASS_NAME, receiver), new PKeyword(KW_OVERWRITE, overwrite)};
}
}

@Builtin(name = J_REGISTER_INTEROP_BEHAVIOR, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 1, takesVarKeywordArgs = true, keywordOnlyNames = {"is_boolean", "is_date",
"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",
"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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@ public abstract class BuiltinNames {

public static final String J___GRAALPYTHON_INTEROP_BEHAVIOR__ = "__graalpython_interop_behavior__";

public static final String J_JAVA_INTEROP_TYPE = "java_interop_type";
public static final String J_REGISTER_JAVA_INTEROP_TYPE = "register_java_interop_type";
public static final TruffleString T_REGISTER_JAVA_INTEROP_TYPE = tsLiteral(J_REGISTER_JAVA_INTEROP_TYPE);

public static final String J_REMOVE_JAVA_INTEROP_TYPE = "remove_java_interop_type";

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ public abstract class ErrorMessages {
public static final TruffleString INTEGER_REQUIRED_GOT = tsLiteral("an integer is required (got type %p)");
public static final TruffleString INTERMEDIATE_OVERFLOW_IN = tsLiteral("intermediate overflow in %s");
public static final TruffleString CANNOT_SET_PROPERTY_ON_INTEROP_EXCEPTION = tsLiteral("Cannot set property on interop exception");
public static final TruffleString INTEROP_TYPE_ALREADY_REGISTERED = tsLiteral("interop type for '%s' already registered");
public static final TruffleString INTEROP_TYPE_NOT_REGISTERED = tsLiteral("interop type for '%s' is not registered");
public static final TruffleString INVALD_OR_UNREADABLE_CLASSPATH = tsLiteral("invalid or unreadable classpath: '%s' - %m");
public static final TruffleString INVALID_ARGS = tsLiteral("%s: invalid arguments");
public static final TruffleString INVALID_BASE_TYPE_OBJ_FOR_CLASS = tsLiteral("Invalid base type object for class %s (base type was '%p' object).");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@
import com.oracle.graal.python.builtins.objects.function.PFunction;
import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
import com.oracle.graal.python.builtins.objects.type.PythonClass;
import com.oracle.graal.python.nodes.HiddenAttr;
import com.oracle.graal.python.nodes.PGuards;
import com.oracle.graal.python.nodes.PNodeWithContext;
import com.oracle.graal.python.nodes.truffle.PythonTypes;
import com.oracle.graal.python.runtime.PythonContext;
import com.oracle.graal.python.runtime.exception.PException;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.dsl.Bind;
Expand All @@ -70,10 +72,15 @@
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.dsl.TypeSystemReference;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.strings.TruffleString;

import java.util.Map;

@TypeSystemReference(PythonTypes.class)
@ImportStatic({PGuards.class})
@GenerateUncached
Expand Down Expand Up @@ -259,7 +266,23 @@ static Object getTruffleException(@SuppressWarnings("unused") AbstractTruffleExc
}

@Fallback
static Object getForeign(@SuppressWarnings("unused") Object object) {
return PythonBuiltinClassType.ForeignObject;
static Object getForeign(@SuppressWarnings("unused") Object object,
@CachedLibrary(limit = "3") InteropLibrary interopLib,
@Bind("this") Node inliningTarget) {
try {
// Retrieve the meta object of the requested object in order to get to the class
Object metaObject = interopLib.getMetaObject(object);
// Get class name from meta object
String truffleClassName = (String) interopLib.getMetaQualifiedName(metaObject);
// Get Registry for custom python types from PythonContext
Map<Object, PythonClass> registry = PythonContext.get(inliningTarget).getInteropTypeRegistry();
if (registry.containsKey(truffleClassName)) {
// If a custom python class was registered, take that one.
return registry.get(truffleClassName);
}
return PythonBuiltinClassType.ForeignObject;
} catch (UnsupportedMessageException e) {
return PythonBuiltinClassType.ForeignObject;
}
}
}
Loading