From 53ef2b17b90a2602c51ffa93894e4a9b0d7ae3aa Mon Sep 17 00:00:00 2001 From: Andrew Strelsky Date: Sun, 1 Sep 2024 12:27:35 -0400 Subject: [PATCH] "Generic" JArray support --- jpype/_core.py | 1 + jpype/_jarray.py | 41 +++++++++++++++++++++++++++++++++++- jpype/_jclass.py | 5 +++++ native/python/pyjp_class.cpp | 5 +++-- test/jpypetest/test_array.py | 12 +++++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index ca1191d01..1bb940289 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -339,6 +339,7 @@ def initializeResources(): _jpype._type_classes[object] = _jpype._java_lang_Object _jpype._type_classes[_jpype.JString] = _jpype._java_lang_String _jpype._type_classes[_jpype.JObject] = _jpype._java_lang_Object + _jpype._type_classes[_jpype.JClass] = _jpype._java_lang_Class _jinit.runJVMInitializers() _jpype.JClass('org.jpype.JPypeKeywords').setKeywords( diff --git a/jpype/_jarray.py b/jpype/_jarray.py index 34ce2bd86..6e8c10131 100644 --- a/jpype/_jarray.py +++ b/jpype/_jarray.py @@ -15,14 +15,37 @@ # See NOTICE file for details. # # ***************************************************************************** +import abc +import collections.abc import _jpype +import typing from . import _jcustomizer __all__ = ['JArray'] -class JArray(_jpype._JObject, internal=True): # type: ignore[call-arg] +T = typing.TypeVar('T') + + +class _JArrayGeneric(collections.abc.Sequence[T]): + + @abc.abstractmethod + def __setitem__(self, index, value): + ... + + def __new__(cls, length: int): + return T.__bound__[length] + + +if typing.TYPE_CHECKING: + _array_base = _JArrayGeneric +else: + # NOTE: the subscript operator needs to be used in the class definition + _array_base = {T : _jpype._JObject} + + +class JArray(_array_base[T], internal=True): # type: ignore[call-arg] """ Creates a Java array class for a Java type of a given dimension. This serves as a base type and factory for all Java array classes. @@ -92,6 +115,18 @@ def __new__(cls, tp, dims=1): def of(cls, array, dtype=None): return _jpype.arrayFromBuffer(array, dtype) + def __class_getitem__(cls, key): + if key is _jpype.JClass: + # explicit check for JClass + # _toJavaClass cannot be used + # passing int, float, etc is not allowed + key = _jpype._java_lang_Class + if isinstance(key, (str, _jpype._java_lang_Class)): + key = _jpype.JClass(key) + if isinstance(key, _jpype.JClass): + return type(key[0]) + raise TypeError("Cannot instantiate unspecified array type") + class _JArrayProto(object): @@ -104,6 +139,10 @@ def __iter__(self): def __reversed__(self): for elem in self[::-1]: yield elem + + def __contains__(self, item): + # in works without this but this should be more efficient + return _jpype.JClass("java.util.Arrays").asList(self).contains(item) def clone(self): """ Clone the Java array. diff --git a/jpype/_jclass.py b/jpype/_jclass.py index faa905f24..3a93c8a93 100644 --- a/jpype/_jclass.py +++ b/jpype/_jclass.py @@ -97,6 +97,11 @@ def __new__(cls, jc, loader=None, initialize=True): # Pass to class factory to create the type return _jpype._getClass(jc) + + @classmethod + def __class_getitem__(cls, index): + # enables JClass[1] to get a Class[] + return JClass("java.lang.Class")[index] class JInterface(_jpype._JObject, internal=True): # type: ignore[call-arg] diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index fc633e11c..9fb4071a0 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -728,8 +728,9 @@ static PyObject *PyJPClass_array(PyJPClass *self, PyObject *item) if (self->m_Class == NULL) { - PyErr_Format(PyExc_TypeError, "Cannot instantiate unspecified array type"); - return NULL; + PyObject *res = PyObject_CallMethod((PyObject *)self, "__class_getitem__", "O", item); + Py_DECREF(item); + return res; } if (PyIndex_Check(item)) diff --git a/test/jpypetest/test_array.py b/test/jpypetest/test_array.py index 6f0c60855..011b56bf6 100644 --- a/test/jpypetest/test_array.py +++ b/test/jpypetest/test_array.py @@ -593,6 +593,7 @@ def testShortcut(self): # Check Objects self.assertEqual(JString[5].getClass(), JArray(JString)(5).getClass()) self.assertEqual(JObject[5].getClass(), JArray(JObject)(5).getClass()) + self.assertEqual(JClass[5].getClass(), JArray(JClass)(5).getClass()) # Test multidimensional self.assertEqual(JDouble[5, 5].getClass(), JArray(JDouble, 2)(5).getClass()) @@ -601,3 +602,14 @@ def testShortcut(self): def testJArrayIndex(self): with self.assertRaises(TypeError): jpype.JArray[10] + + def testJArrayGeneric(self): + self.assertEqual(type(JObject[0]), JArray(JObject)) + + def testJArrayGeneric_Init(self): + Arrays = JClass("java.util.Arrays") + self.assertTrue(Arrays.equals(JObject[0], JArray(JObject)(0))) + + def testJArrayInvalidGeneric(self): + with self.assertRaises(TypeError): + jpype.JArray[object]