From 18313e340141f15b5b7da0bd038fd0d7dc571f25 Mon Sep 17 00:00:00 2001 From: Andrew Strelsky <46897303+astrelsky@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:32:41 -0400 Subject: [PATCH] "Generic" JArray support --- jpype/_core.py | 1 + jpype/_jarray.py | 16 ++++++++++++++ jpype/_jarray.pyi | 43 ++++++++++++++++++++++++++++++++++++ jpype/_jclass.py | 5 +++++ native/python/pyjp_class.cpp | 5 +++-- test/jpypetest/test_array.py | 12 ++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 jpype/_jarray.pyi 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..9510c74cb 100644 --- a/jpype/_jarray.py +++ b/jpype/_jarray.py @@ -92,6 +92,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 +116,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/_jarray.pyi b/jpype/_jarray.pyi new file mode 100644 index 000000000..10b8de728 --- /dev/null +++ b/jpype/_jarray.pyi @@ -0,0 +1,43 @@ +# ***************************************************************************** +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# See NOTICE file for details. +# +# ***************************************************************************** +import abc +import collections.abc +import typing + + +__all__ = ['JArray'] + + +T = typing.TypeVar('T') + + +class _JArrayGeneric(collections.abc.Sequence[T]): + + @abc.abstractmethod + def __setitem__(self, index, value): + ... + + +class JArray(_JArrayGeneric[T], metaclass=abc.ABCMeta): + + def __new__(cls, tp, dims=1): + ... + + @classmethod + def of(cls, array, dtype=None): + ... 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]