From 0f153a5442a819e21fe668e776aee529ac41f06a Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 5 May 2022 12:25:12 +0200 Subject: [PATCH 01/28] Support PathLike types for classpath and jvmpath. Closes https://github.com/jpype-project/jpype/issues/529 --- jpype/_classpath.py | 8 ++- jpype/_core.py | 128 +++++++++++++++++++-------------- test/jpypetest/test_startup.py | 58 ++++++++++----- 3 files changed, 120 insertions(+), 74 deletions(-) diff --git a/jpype/_classpath.py b/jpype/_classpath.py index 78383ed2d..2d8c919eb 100644 --- a/jpype/_classpath.py +++ b/jpype/_classpath.py @@ -16,6 +16,8 @@ # # ***************************************************************************** import os as _os +import typing + import _jpype __all__ = ['addClassPath', 'getClassPath'] @@ -24,7 +26,7 @@ _SEP = _os.path.pathsep -def addClassPath(path1): +def addClassPath(path1: typing.Union[str, _os.PathLike]) -> None: """ Add a path to the Java class path Classpath items can be a java, a directory, or a @@ -66,7 +68,7 @@ def addClassPath(path1): _CLASSPATHS.append(path1) -def getClassPath(env=True): +def getClassPath(env: bool = True) -> str: """ Get the full Java class path. Includes user added paths and the environment CLASSPATH. @@ -79,7 +81,7 @@ def getClassPath(env=True): global _CLASSPATHS global _SEP - # Merge the evironment path + # Merge the environment path classPath = list(_CLASSPATHS) envPath = _os.environ.get("CLASSPATH") if env and envPath: diff --git a/jpype/_core.py b/jpype/_core.py index a71935535..bb634f4c6 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -15,8 +15,13 @@ # See NOTICE file for details. # # ***************************************************************************** -import sys +from __future__ import annotations + import atexit +import os +import sys +import typing + import _jpype from . import types as _jtypes from . import _classpath @@ -61,6 +66,10 @@ def versionTest(): pass +if typing.TYPE_CHECKING: + _PathOrStr = typing.Union[str, os.PathLike] + + # See http://scottlobdell.me/2015/04/decorators-arguments-python/ def deprecated(*args): """ Marks a function a deprecated when used as decorator. @@ -104,23 +113,48 @@ def isJVMStarted(): return _jpype.isStarted() -def _hasClassPath(args): +def _hasClassPath(args: typing.Tuple[_PathOrStr, ...]) -> bool: for i in args: - if i.startswith('-Djava.class.path'): + if isinstance(i, str) and i.startswith('-Djava.class.path'): return True return False -def _handleClassPath(clsList): +def _handleClassPath( + classpath: typing.Union[ + _PathOrStr, + typing.Tuple[_PathOrStr, ...] + ], +) -> str: + """ + Return a classpath which represents the given tuple of classpath specifications + """ out = [] - for s in clsList: - if not isinstance(s, str): - raise TypeError("Classpath elements must be strings") - if s.endswith('*'): + + if isinstance(classpath, (str, os.PathLike)): + classpath = (classpath,) + try: + # Convert anything iterable into a tuple. + classpath = tuple(classpath) + except TypeError: + raise TypeError("Unknown class path element") + + for element in classpath: + try: + pth = os.fspath(element) + except TypeError as err: + raise TypeError("Classpath elements must be strings or Path-like") from err + + if isinstance(pth, bytes): + # In the future we may allow this to support Paths which are undecodable. + # https://docs.python.org/3/howto/unicode.html#unicode-filenames. + raise TypeError("Classpath elements must be strings or Path-like") + + if pth.endswith('*'): import glob - out.extend(glob.glob(s + '.jar')) + out.extend(glob.glob(pth + '.jar')) else: - out.append(s) + out.append(pth) return _classpath._SEP.join(out) @@ -131,7 +165,14 @@ def interactive(): return bool(getattr(sys, 'ps1', sys.flags.interactive)) -def startJVM(*args, **kwargs): +def startJVM( + *jvmargs: str, + jvmpath: typing.Optional[_PathOrStr] = None, + classpath: typing.Optional[_PathOrStr] = None, + ignoreUnrecognized: bool = False, + convertStrings: bool = False, + interrupt: bool = not interactive(), +) -> None: """ Starts a Java Virtual Machine. Without options it will start the JVM with the default classpath and jvmpath. @@ -140,14 +181,14 @@ def startJVM(*args, **kwargs): The default jvmpath is determined by ``jpype.getDefaultJVMPath()``. Parameters: - *args (Optional, str[]): Arguments to give to the JVM. - The first argument may be the path the JVM. + *jvmargs (Optional, str[]): Arguments to give to the JVM. + The first argument may be the path to the JVM. Keyword Arguments: - jvmpath (str): Path to the jvm library file, + jvmpath (str, PathLike): Path to the jvm library file, Typically one of (``libjvm.so``, ``jvm.dll``, ...) Using None will apply the default jvmpath. - classpath (str,[str]): Set the classpath for the JVM. + classpath (str, PathLike, [str, PathLike]): Set the classpath for the JVM. This will override any classpath supplied in the arguments list. A value of None will give no classpath to JVM. ignoreUnrecognized (bool): Option to ignore @@ -166,8 +207,7 @@ def startJVM(*args, **kwargs): Raises: OSError: if the JVM cannot be started or is already running. - TypeError: if an invalid keyword argument is supplied - or a keyword argument conflicts with the arguments. + TypeError: if a keyword argument conflicts with the positional arguments. """ if _jpype.isStarted(): @@ -176,54 +216,39 @@ def startJVM(*args, **kwargs): if _JVM_started: raise OSError('JVM cannot be restarted') - args = list(args) - # JVM path - jvmpath = None - if args: + if jvmargs: # jvm is the first argument the first argument is a path or None - if not args[0] or not args[0].startswith('-'): - jvmpath = args.pop(0) - if 'jvmpath' in kwargs: - if jvmpath: - raise TypeError('jvmpath specified twice') - jvmpath = kwargs.pop('jvmpath') + if jvmargs[0] is not None and isinstance(jvmargs[0], str) and not jvmargs[0].startswith('-'): + if jvmpath: + raise TypeError('jvmpath specified twice') + jvmpath = jvmargs[0] + jvmargs = jvmargs[1:] + if not jvmpath: jvmpath = getDefaultJVMPath() + else: + # Allow the path to be a PathLike. + jvmpath = os.fspath(jvmpath) + + extra_jvm_args = tuple() # Classpath handling - if _hasClassPath(args): + if _hasClassPath(jvmargs): # Old style, specified in the arguments - if 'classpath' in kwargs: + if classpath is not None: # Cannot apply both styles, conflict raise TypeError('classpath specified twice') - classpath = None - elif 'classpath' in kwargs: - # New style, as a keywork - classpath = kwargs.pop('classpath') - else: - # Not speficied at all, use the default classpath + elif classpath is None: + # Not specified at all, use the default classpath. classpath = _classpath.getClassPath() # Handle strings and list of strings. if classpath: - if isinstance(classpath, str): - args.append('-Djava.class.path=%s' % _handleClassPath([classpath])) - elif hasattr(classpath, '__iter__'): - args.append('-Djava.class.path=%s' % _handleClassPath(classpath)) - else: - raise TypeError("Unknown class path element") - - ignoreUnrecognized = kwargs.pop('ignoreUnrecognized', False) - convertStrings = kwargs.pop('convertStrings', False) - interrupt = kwargs.pop('interrupt', not interactive()) - - if kwargs: - raise TypeError("startJVM() got an unexpected keyword argument '%s'" - % (','.join([str(i) for i in kwargs]))) + extra_jvm_args += (f'-Djava.class.path={_handleClassPath(classpath)}', ) try: - _jpype.startup(jvmpath, tuple(args), + _jpype.startup(jvmpath, jvmargs + extra_jvm_args, ignoreUnrecognized, convertStrings, interrupt) initializeResources() except RuntimeError as ex: @@ -233,8 +258,7 @@ def startJVM(*args, **kwargs): match = re.search(r"([0-9]+)\.[0-9]+", source) if match: version = int(match.group(1)) - 44 - raise RuntimeError("%s is older than required Java version %d" % ( - jvmpath, version)) from ex + raise RuntimeError(f"{jvmpath} is older than required Java version{version}") from ex raise diff --git a/test/jpypetest/test_startup.py b/test/jpypetest/test_startup.py index c64d99cbf..0dd609a9c 100644 --- a/test/jpypetest/test_startup.py +++ b/test/jpypetest/test_startup.py @@ -18,23 +18,20 @@ import jpype import common import subrun +import functools import os +from pathlib import Path import sys import unittest -def runStartJVM(*args, **kwargs): - jpype.startJVM(*args, **kwargs) - - +@functools.wraps(jpype.startJVM) def runStartJVMTest(*args, **kwargs): jpype.startJVM(*args, **kwargs) try: - jclass = jpype.JClass('jpype.array.TestArray') - return - except: - pass - raise RuntimeError("Test class not found") + assert jpype.JClass('jpype.array.TestArray') is not None + except Exception as err: + raise RuntimeError("Test class not found") from err root = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) @@ -57,17 +54,18 @@ def testRestart(self): jpype.shutdownJVM() jpype.startJVM(convertStrings=False) - def testJVMPathKeyword(self): - runStartJVM(jvmpath=self.jvmpath) - def testInvalidArgsFalse(self): with self.assertRaises(RuntimeError): - runStartJVM("-for_sure_InVaLiD", - ignoreUnrecognized=False, convertStrings=False) + jpype.startJVM( + "-for_sure_InVaLiD", + ignoreUnrecognized=False, convertStrings=False, + ) def testInvalidArgsTrue(self): - runStartJVM("-for_sure_InVaLiD", - ignoreUnrecognized=True, convertStrings=False) + jpype.startJVM( + "-for_sure_InVaLiD", + ignoreUnrecognized=True, convertStrings=False, + ) def testClasspathArgKeyword(self): runStartJVMTest(classpath=cp, convertStrings=False) @@ -81,6 +79,16 @@ def testClasspathArgListEmpty(self): def testClasspathArgDef(self): runStartJVMTest('-Djava.class.path=%s' % cp, convertStrings=False) + def testClasspathArgPath(self): + runStartJVMTest(classpath=Path(cp), convertStrings=False) + + def testClasspathArgPathList(self): + runStartJVMTest(classpath=[Path(cp)], convertStrings=False) + + def testClasspathArgGlob(self): + jpype.startJVM(classpath=os.path.join(cp, '..', 'jar', 'mrjar*')) + assert jpype.JClass('org.jpype.mrjar.A') is not None + def testClasspathTwice(self): with self.assertRaises(TypeError): runStartJVMTest('-Djava.class.path=%s' % @@ -90,14 +98,26 @@ def testClasspathBadType(self): with self.assertRaises(TypeError): runStartJVMTest(classpath=1, convertStrings=False) - def testPathArg(self): + def testJVMPathArg_Str(self): runStartJVMTest(self.jvmpath, classpath=cp, convertStrings=False) - def testPathKeyword(self): - path = jpype.getDefaultJVMPath() + def testJVMPathArg_Path(self): + with self.assertRaises(TypeError): + runStartJVMTest([ + # Pass a path as the first argument. This isn't supported (this is + # reflected in the type definition), but the fact that it "works" + # gives rise to this test. + Path(self.jvmpath), cp], # type: ignore + convertStrings=False, + ) + + def testJVMPathKeyword_str(self): runStartJVMTest(classpath=cp, jvmpath=self.jvmpath, convertStrings=False) + def testJVMPathKeyword_Path(self): + runStartJVMTest(jvmpath=Path(self.jvmpath), classpath=cp, convertStrings=False) + def testPathTwice(self): with self.assertRaises(TypeError): jpype.startJVM(self.jvmpath, jvmpath=self.jvmpath) From 053dbd5be1e522df20d26c0ea0757de58129fa96 Mon Sep 17 00:00:00 2001 From: "Karl E. Nelson" Date: Mon, 3 Apr 2023 13:20:27 -0700 Subject: [PATCH 02/28] Change to use .equals --- doc/CHANGELOG.rst | 3 +++ native/python/pyjp_object.cpp | 4 ++-- test/jpypetest/test_comparable.py | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/CHANGELOG.rst b/doc/CHANGELOG.rst index 123970f5c..79be55859 100644 --- a/doc/CHANGELOG.rst +++ b/doc/CHANGELOG.rst @@ -6,6 +6,9 @@ This changelog *only* contains changes from the *first* pypi release (0.5.4.3) o Latest Changes: - **1.4.2_dev0 - 2022-10-26** + - Switched ``__eq__`` and ``__ne__`` operator to use ``.equals`` for comparable objects to + avoid exception when comparing object of different types. + - Fixed crash when calling subscript on JArray. - Fixed direct byte buffers not reporting nbytes correctly when cast to memoryview. diff --git a/native/python/pyjp_object.cpp b/native/python/pyjp_object.cpp index 494175f12..735cdf1e1 100644 --- a/native/python/pyjp_object.cpp +++ b/native/python/pyjp_object.cpp @@ -164,14 +164,14 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) Py_RETURN_TRUE; if (null0 || null1) Py_RETURN_FALSE; - return PyBool_FromLong(frame.compareTo(obj0, obj1) == 0); + return PyBool_FromLong(frame.equals(obj0, obj1)); case Py_NE: if (null0 && null1) Py_RETURN_FALSE; if (null0 || null1) Py_RETURN_TRUE; - return PyBool_FromLong(frame.compareTo(obj0, obj1) != 0); + return PyBool_FromLong(!frame.equals(obj0, obj1)); case Py_LT: if (null0 || null1) break; diff --git a/test/jpypetest/test_comparable.py b/test/jpypetest/test_comparable.py index 273bdc2de..c6eeb197f 100644 --- a/test/jpypetest/test_comparable.py +++ b/test/jpypetest/test_comparable.py @@ -79,3 +79,9 @@ def testComparableNull(self): print(i3 > i3) with self.assertRaises(ValueError): print(i3 >= i3) + + def testComparableObj(self): + O1 = jpype.JClass("java.time.temporal.ChronoUnit").SECONDS + O2 = jpype.JClass("java.util.concurrent.TimeUnit").SECONDS + self.assertTrue(O1 != O2) + self.assertFalse(O1 == O2) From a358ca3e14b0820679a68ad0b0457c9f4074d2d2 Mon Sep 17 00:00:00 2001 From: "Karl E. Nelson" Date: Mon, 3 Apr 2023 14:01:54 -0700 Subject: [PATCH 03/28] Fixed segfault found in coverage test. --- native/python/pyjp_object.cpp | 2 +- test/jpypetest/test_comparable.py | 21 +++++++++++++++++-- test/jpypetest/test_objectwrapper.py | 31 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/native/python/pyjp_object.cpp b/native/python/pyjp_object.cpp index 735cdf1e1..c47429733 100644 --- a/native/python/pyjp_object.cpp +++ b/native/python/pyjp_object.cpp @@ -154,7 +154,7 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) return out; } obj1 = match.convert().l; - } else if (!null1 && javaSlot1 != NULL) + } else if (!null1 && javaSlot1 != NULL && !javaSlot1->getClass()->isPrimitive()) obj1 = javaSlot1->getValue().l; switch (op) diff --git a/test/jpypetest/test_comparable.py b/test/jpypetest/test_comparable.py index c6eeb197f..4b91107da 100644 --- a/test/jpypetest/test_comparable.py +++ b/test/jpypetest/test_comparable.py @@ -81,7 +81,24 @@ def testComparableNull(self): print(i3 >= i3) def testComparableObj(self): - O1 = jpype.JClass("java.time.temporal.ChronoUnit").SECONDS - O2 = jpype.JClass("java.util.concurrent.TimeUnit").SECONDS + C1 = jpype.JClass("java.time.temporal.ChronoUnit") + C2 = jpype.JClass("java.util.concurrent.TimeUnit") + O1 = C1.SECONDS + O2 = C2.SECONDS + N1 = jpype.JObject(None, C1) + N2 = jpype.JObject(None, C2) + V = jpype.JInt(1) + # Test dissimilar objects self.assertTrue(O1 != O2) self.assertFalse(O1 == O2) + # test Nulls + self.assertTrue(N1 == N2) + self.assertFalse(N1 != N2) + self.assertTrue(N1 == None) + self.assertFalse(N1 != None) + # test primitives + self.assertFalse(O1 == V) + self.assertFalse(V == O1) + # test null to primitives + self.assertFalse(N1 == V) + self.assertFalse(V == N1) diff --git a/test/jpypetest/test_objectwrapper.py b/test/jpypetest/test_objectwrapper.py index 80f18c7eb..f6a64cf85 100644 --- a/test/jpypetest/test_objectwrapper.py +++ b/test/jpypetest/test_objectwrapper.py @@ -113,6 +113,37 @@ class Fred(object): with self.assertRaises(TypeError): jpype.JObject(Fred()) + def testObjectEq(self): + C1 = jpype.JClass("java.lang.StringBuffer") + C2 = jpype.JClass("java.lang.Exception") + O1 = C1() + O2 = C2() + N1 = jpype.JObject(None, C1) + N2 = jpype.JObject(None, C2) + V = jpype.JInt(1) + # Test dissimilar objects + self.assertTrue(O1 != O2) + self.assertFalse(O1 == O2) + self.assertTrue(O2 != O1) + self.assertFalse(O2 == O1) + # test Nulls + self.assertTrue(N1 == N2) + self.assertFalse(N1 != N2) + self.assertTrue(N1 == None) + self.assertFalse(N1 != None) + self.assertTrue(None == N1) + self.assertFalse(None != N1) + # test primitives + self.assertFalse(O1 == V) + self.assertFalse(V == O1) + self.assertFalse(O2 == V) + self.assertFalse(V == O2) + # test null to primitives + self.assertFalse(N1 == V) + self.assertFalse(V == N1) + self.assertFalse(N2 == V) + self.assertFalse(V == N2) + # def testMakeSureWeCanLoadAllClasses(self): # def get_system_jars(): From 77f74c03d3e8c0109c345fb2e88eb8d96f21e3be Mon Sep 17 00:00:00 2001 From: "Karl E. Nelson" Date: Mon, 3 Apr 2023 14:53:57 -0700 Subject: [PATCH 04/28] Edge cases --- native/common/jp_javaframe.cpp | 8 +- native/python/pyjp_object.cpp | 13 +++- test/harness/java8/jpype/method/Caller.java | 81 +++++++++++++++++++++ test/jpypetest/test_comparable.py | 17 +++++ 4 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 test/harness/java8/jpype/method/Caller.java diff --git a/native/common/jp_javaframe.cpp b/native/common/jp_javaframe.cpp index d05b0480a..42d5739e9 100644 --- a/native/common/jp_javaframe.cpp +++ b/native/common/jp_javaframe.cpp @@ -1194,7 +1194,13 @@ jint JPJavaFrame::compareTo(jobject obj, jobject obj2) { jvalue v; v.l = obj2; - return CallIntMethodA(obj, m_Context->m_CompareToID, &v); + jint ret = m_Env->CallIntMethodA(obj, m_Context->m_CompareToID, &v); + if (m_Env->ExceptionOccurred()) + { + m_Env->ExceptionClear(); + JP_RAISE(PyExc_TypeError, "Unable to compare") + } + return ret; } jthrowable JPJavaFrame::getCause(jthrowable th) diff --git a/native/python/pyjp_object.cpp b/native/python/pyjp_object.cpp index c47429733..0e47f4fb6 100644 --- a/native/python/pyjp_object.cpp +++ b/native/python/pyjp_object.cpp @@ -55,8 +55,6 @@ static PyObject *PyJPObject_compare(PyObject *self, PyObject *other, int op) if (op == Py_NE) { PyObject *ret = PyJPObject_compare(self, other, Py_EQ); - if (ret == NULL) - return NULL; int rc = (ret == Py_False); Py_DECREF(ret); return PyBool_FromLong(rc); @@ -120,7 +118,7 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) bool null1 = false; // First slot is Null - if (self == Py_None || javaSlot0 == NULL || + if (javaSlot0 == NULL || (!javaSlot0->getClass()->isPrimitive() && javaSlot0->getValue().l == NULL)) null0 = true; if (other == Py_None || (javaSlot1 != NULL && @@ -153,6 +151,14 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) Py_INCREF(out); return out; } + if (match.type < JPMatch::Type::_implicit) + { + if (op == Py_EQ || op == Py_NE) + return PyBool_FromLong(op == Py_NE); + PyObject *out = Py_NotImplemented; + Py_INCREF(out); + return out; + } obj1 = match.convert().l; } else if (!null1 && javaSlot1 != NULL && !javaSlot1->getClass()->isPrimitive()) obj1 = javaSlot1->getValue().l; @@ -165,7 +171,6 @@ static PyObject *PyJPComparable_compare(PyObject *self, PyObject *other, int op) if (null0 || null1) Py_RETURN_FALSE; return PyBool_FromLong(frame.equals(obj0, obj1)); - case Py_NE: if (null0 && null1) Py_RETURN_FALSE; diff --git a/test/harness/java8/jpype/method/Caller.java b/test/harness/java8/jpype/method/Caller.java new file mode 100644 index 000000000..d6c986395 --- /dev/null +++ b/test/harness/java8/jpype/method/Caller.java @@ -0,0 +1,81 @@ +/* **************************************************************************** + 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. +**************************************************************************** */ +package jpype.method; + +/** + * Caller sensitive methods need special handling. + * + * So we need to test each possible path (different return types and arguments). + * + * We will pretend our methods are also CallerSensitive. + */ +public class Caller +{ + + public static Object callObjectStatic() + { + return new Caller(); + } + + public Object callObjectMember() + { + return new Caller(); + } + + public static void callVoidStatic() + { + } + + public void callVoidMember() + { + } + + public static int callIntegerStatic() + { + return 123; + } + + public int callIntegerMember() + { + return 123; + } + + public Object callArgs(Object a, Object b) + { + return b; + } + + public int callArg1(int a) + { + return a; + } + + public Object callVarArgs(Object a, Object... b) + { + return b; + } + + public Class callStackWalker1() + { + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass(); + } + + public Class callStackWalker2() + { + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass(); + } + +}; diff --git a/test/jpypetest/test_comparable.py b/test/jpypetest/test_comparable.py index 4b91107da..ce54b223f 100644 --- a/test/jpypetest/test_comparable.py +++ b/test/jpypetest/test_comparable.py @@ -102,3 +102,20 @@ def testComparableObj(self): # test null to primitives self.assertFalse(N1 == V) self.assertFalse(V == N1) + + self.assertFalse(1 == O1) + self.assertFalse("M" == O1) + self.assertFalse(O1 == 1) + self.assertFalse(O1 == "M") + + self.assertTrue(1 != O1) + self.assertTrue("M" != O1) + self.assertTrue(O1 != 1) + self.assertTrue(O1 != "M") + + with self.assertRaises(TypeError): + self.assertTrue(O1 > 1) + with self.assertRaises(TypeError): + self.assertTrue(1 > O1) + with self.assertRaises(TypeError): + self.assertTrue(O1 > O2) From d3c171727b5d38308243b84aa9a4f3f534fb684b Mon Sep 17 00:00:00 2001 From: "Karl E. Nelson" Date: Mon, 3 Apr 2023 14:56:50 -0700 Subject: [PATCH 05/28] Remove out of date file --- test/harness/java8/jpype/method/Caller.java | 81 --------------------- 1 file changed, 81 deletions(-) delete mode 100644 test/harness/java8/jpype/method/Caller.java diff --git a/test/harness/java8/jpype/method/Caller.java b/test/harness/java8/jpype/method/Caller.java deleted file mode 100644 index d6c986395..000000000 --- a/test/harness/java8/jpype/method/Caller.java +++ /dev/null @@ -1,81 +0,0 @@ -/* **************************************************************************** - 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. -**************************************************************************** */ -package jpype.method; - -/** - * Caller sensitive methods need special handling. - * - * So we need to test each possible path (different return types and arguments). - * - * We will pretend our methods are also CallerSensitive. - */ -public class Caller -{ - - public static Object callObjectStatic() - { - return new Caller(); - } - - public Object callObjectMember() - { - return new Caller(); - } - - public static void callVoidStatic() - { - } - - public void callVoidMember() - { - } - - public static int callIntegerStatic() - { - return 123; - } - - public int callIntegerMember() - { - return 123; - } - - public Object callArgs(Object a, Object b) - { - return b; - } - - public int callArg1(int a) - { - return a; - } - - public Object callVarArgs(Object a, Object... b) - { - return b; - } - - public Class callStackWalker1() - { - return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass(); - } - - public Class callStackWalker2() - { - return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass(); - } - -}; From 6fc67a49341e826da250d1a609f6dbf05789438a Mon Sep 17 00:00:00 2001 From: "Karl E. Nelson" Date: Tue, 4 Apr 2023 09:08:00 -0700 Subject: [PATCH 06/28] Changed documentation --- doc/CHANGELOG.rst | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/CHANGELOG.rst b/doc/CHANGELOG.rst index 79be55859..7ca44c7e5 100644 --- a/doc/CHANGELOG.rst +++ b/doc/CHANGELOG.rst @@ -6,13 +6,21 @@ This changelog *only* contains changes from the *first* pypi release (0.5.4.3) o Latest Changes: - **1.4.2_dev0 - 2022-10-26** - - Switched ``__eq__`` and ``__ne__`` operator to use ``.equals`` for comparable objects to - avoid exception when comparing object of different types. + - Switched ``__eq__`` and ``__ne__`` operator to use ``equals`` rather than + ``compareTo`` for comparable objects to avoid exception when comparing + object of different types. + + - Fixed segmentation fault when comparing Java Comparable to primitives. + + - Java exceptions that occur in inequality comparisons now map to Python + TypeError. - Fixed crash when calling subscript on JArray. - - Fixed direct byte buffers not reporting nbytes correctly when cast to memoryview. - - Expand the defintion for Functional interface to include classes without + - Fixed direct byte buffers not reporting nbytes correctly when cast to + memoryview. + + - Expand the defintion for Functional interface to include classes without FunctionInterface annotation. - **1.4.1 - 2022-10-26** From 46f7739d0cec297a1975932e068063afe32ac056 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sat, 29 Apr 2023 13:25:58 +0200 Subject: [PATCH 07/28] [doc/install.rst] tested on jdk8+ fixes #1083 --- doc/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install.rst b/doc/install.rst index 9fab4ae32..e5453dce8 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -34,7 +34,7 @@ Java JPype source distribution includes a copy of the Java JNI header and precompiled Java code, thus the Java Development Kit (JDK) is not required. - JPype has been tested with Java versions from Java 1.7 to Java 13. + JPype has been tested with Java versions from Java 1.8 to Java 13. C++ A C++ compiler which matches the ABI used to build CPython. From d84735042227381f5cdde9f70a58dec7cb071d6c Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Tue, 2 May 2023 17:14:22 +0200 Subject: [PATCH 08/28] Handle None being passed explicitly to startJVM --- jpype/_core.py | 2 +- test/jpypetest/test_startup.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index bdc4a0e3c..07f246c5d 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -211,7 +211,7 @@ def startJVM( # JVM path if jvmargs: # jvm is the first argument the first argument is a path or None - if jvmargs[0] is not None and isinstance(jvmargs[0], str) and not jvmargs[0].startswith('-'): + if jvmargs[0] is None or (isinstance(jvmargs[0], str) and not jvmargs[0].startswith('-')): if jvmpath: raise TypeError('jvmpath specified twice') jvmpath = jvmargs[0] diff --git a/test/jpypetest/test_startup.py b/test/jpypetest/test_startup.py index 0dd609a9c..ca99424e2 100644 --- a/test/jpypetest/test_startup.py +++ b/test/jpypetest/test_startup.py @@ -16,12 +16,10 @@ # # ***************************************************************************** import jpype -import common import subrun import functools import os from pathlib import Path -import sys import unittest @@ -101,13 +99,21 @@ def testClasspathBadType(self): def testJVMPathArg_Str(self): runStartJVMTest(self.jvmpath, classpath=cp, convertStrings=False) + def testJVMPathArg_None(self): + # It is allowed to pass None as a JVM path + runStartJVMTest(None, classpath=cp, ) + + def testJVMPathArg_NoArgs(self): + runStartJVMTest(classpath=cp) + def testJVMPathArg_Path(self): with self.assertRaises(TypeError): - runStartJVMTest([ + runStartJVMTest( # Pass a path as the first argument. This isn't supported (this is # reflected in the type definition), but the fact that it "works" # gives rise to this test. - Path(self.jvmpath), cp], # type: ignore + Path(self.jvmpath), # type: ignore + classpath=cp, convertStrings=False, ) From 507ede7b53de355b33eca25abb02a9ff84ae46f0 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 3 May 2023 10:23:32 +0200 Subject: [PATCH 09/28] Systematically check that jpype.startJVM is tested and correctly typed --- jpype/_core.py | 4 +- jpype/_core.pyi | 2 - test/jpypetest/test_startup.py | 73 ++++++++++++++++++++-------------- 3 files changed, 46 insertions(+), 33 deletions(-) delete mode 100644 jpype/_core.pyi diff --git a/jpype/_core.py b/jpype/_core.py index 07f246c5d..8af9ab8f8 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -160,7 +160,7 @@ def interactive(): def startJVM( *jvmargs: str, jvmpath: typing.Optional[_PathOrStr] = None, - classpath: typing.Optional[_PathOrStr] = None, + classpath: typing.Optional[typing.Sequence[_PathOrStr], _PathOrStr] = None, ignoreUnrecognized: bool = False, convertStrings: bool = False, interrupt: bool = not interactive(), @@ -223,7 +223,7 @@ def startJVM( # Allow the path to be a PathLike. jvmpath = os.fspath(jvmpath) - extra_jvm_args = tuple() + extra_jvm_args: typing.Tuple[str, ...] = tuple() # Classpath handling if _hasClassPath(jvmargs): diff --git a/jpype/_core.pyi b/jpype/_core.pyi deleted file mode 100644 index f4eb32c79..000000000 --- a/jpype/_core.pyi +++ /dev/null @@ -1,2 +0,0 @@ -class _JRuntime: - pass diff --git a/test/jpypetest/test_startup.py b/test/jpypetest/test_startup.py index ca99424e2..350971f09 100644 --- a/test/jpypetest/test_startup.py +++ b/test/jpypetest/test_startup.py @@ -22,16 +22,6 @@ from pathlib import Path import unittest - -@functools.wraps(jpype.startJVM) -def runStartJVMTest(*args, **kwargs): - jpype.startJVM(*args, **kwargs) - try: - assert jpype.JClass('jpype.array.TestArray') is not None - except Exception as err: - raise RuntimeError("Test class not found") from err - - root = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) cp = os.path.join(root, 'classes').replace('\\', '/') @@ -62,26 +52,39 @@ def testInvalidArgsFalse(self): def testInvalidArgsTrue(self): jpype.startJVM( "-for_sure_InVaLiD", - ignoreUnrecognized=True, convertStrings=False, + ignoreUnrecognized=True, + convertStrings=False, ) def testClasspathArgKeyword(self): - runStartJVMTest(classpath=cp, convertStrings=False) + jpype.startJVM(classpath=cp, convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgList(self): - runStartJVMTest(classpath=[cp], convertStrings=False) + jpype.startJVM( + classpath=[cp], + convertStrings=False, + ) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgListEmpty(self): - runStartJVMTest(classpath=[cp, ''], convertStrings=False) + jpype.startJVM( + classpath=[cp, ''], + convertStrings=False, + ) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgDef(self): - runStartJVMTest('-Djava.class.path=%s' % cp, convertStrings=False) + jpype.startJVM('-Djava.class.path=%s' % cp, convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgPath(self): - runStartJVMTest(classpath=Path(cp), convertStrings=False) + jpype.startJVM(classpath=Path(cp), convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgPathList(self): - runStartJVMTest(classpath=[Path(cp)], convertStrings=False) + jpype.startJVM(classpath=[Path(cp)], convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testClasspathArgGlob(self): jpype.startJVM(classpath=os.path.join(cp, '..', 'jar', 'mrjar*')) @@ -89,40 +92,52 @@ def testClasspathArgGlob(self): def testClasspathTwice(self): with self.assertRaises(TypeError): - runStartJVMTest('-Djava.class.path=%s' % + jpype.startJVM('-Djava.class.path=%s' % cp, classpath=cp, convertStrings=False) def testClasspathBadType(self): with self.assertRaises(TypeError): - runStartJVMTest(classpath=1, convertStrings=False) + jpype.startJVM(classpath=1, convertStrings=False) def testJVMPathArg_Str(self): - runStartJVMTest(self.jvmpath, classpath=cp, convertStrings=False) + jpype.startJVM(self.jvmpath, classpath=cp, convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testJVMPathArg_None(self): # It is allowed to pass None as a JVM path - runStartJVMTest(None, classpath=cp, ) + jpype.startJVM( + None, # type: ignore + classpath=cp, + ) + assert jpype.JClass('jpype.array.TestArray') is not None def testJVMPathArg_NoArgs(self): - runStartJVMTest(classpath=cp) + jpype.startJVM( + classpath=cp, + ) + assert jpype.JClass('jpype.array.TestArray') is not None def testJVMPathArg_Path(self): with self.assertRaises(TypeError): - runStartJVMTest( + jpype.startJVM( # Pass a path as the first argument. This isn't supported (this is # reflected in the type definition), but the fact that it "works" # gives rise to this test. - Path(self.jvmpath), # type: ignore - classpath=cp, + Path(self.jvmpath), # type: ignore convertStrings=False, ) def testJVMPathKeyword_str(self): - runStartJVMTest(classpath=cp, jvmpath=self.jvmpath, - convertStrings=False) + jpype.startJVM( + classpath=cp, + jvmpath=self.jvmpath, + convertStrings=False, + ) + assert jpype.JClass('jpype.array.TestArray') is not None def testJVMPathKeyword_Path(self): - runStartJVMTest(jvmpath=Path(self.jvmpath), classpath=cp, convertStrings=False) + jpype.startJVM(jvmpath=Path(self.jvmpath), classpath=cp, convertStrings=False) + assert jpype.JClass('jpype.array.TestArray') is not None def testPathTwice(self): with self.assertRaises(TypeError): @@ -130,4 +145,4 @@ def testPathTwice(self): def testBadKeyword(self): with self.assertRaises(TypeError): - jpype.startJVM(invalid=True) + jpype.startJVM(invalid=True) # type: ignore From 5744ba25f6fdd6456c6af02dba998d0d4cc3705e Mon Sep 17 00:00:00 2001 From: Dennis Soemers Date: Tue, 30 May 2023 16:35:18 +0200 Subject: [PATCH 10/28] Fixed typos in userguide.rst --- doc/userguide.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/userguide.rst b/doc/userguide.rst index 3fd9bb467..bd7874158 100644 --- a/doc/userguide.rst +++ b/doc/userguide.rst @@ -36,11 +36,11 @@ hard at work on your latest project but you just need to pip in the database driver for your customers database and you can call it a night. Unfortunately, it appears that your customers database will not connect to the Python database API. The whole thing is custom and the customer isn't going to supply you with -a Python version. They did sent you a Java driver for the database but fat +a Python version. They did send you a Java driver for the database but fat lot of good that will do for you. Stumbling through the internet you find a module that says it can natively -load Java packages as Python modules. Well, it worth a shot... +load Java packages as Python modules. Well, it's worth a shot... So first thing the guide says is that you need to install Java and set up a ``JAVA_HOME`` environment variable pointing to the JRE. Then start the @@ -305,9 +305,9 @@ design goals. - Favor clarity over performance. This doesn't mean not trying to optimize paths, but just as premature optimization is the bane of programmers, requiring writing to maximize speed is a poor long term choice, especially - in a language such as Python were weak typing can promote bit rot. + in a language such as Python where weak typing can promote bit rot. -- If a new method has to be introduced, make look familiar. +- If a new method has to be introduced, make it look familiar. Java programmers look to a method named "of" to convert to a type on factories such as a Stream, thus ``JArray.of`` converts a Python NumPy array to Java. Python programmers expect that memory backed objects can be converted @@ -546,7 +546,7 @@ JPype Concepts *************** At its heart, JPype is about providing a bridge to use Java within Python. -Depending on your prospective that can either be a means of accessing Java +Depending on your perspective that can either be a means of accessing Java libraries from within Python or a way to use Java using Python syntax for interactivity and visualization. This mean not only exposing a limited API but instead trying to provide the entirety of the Java language with Python. @@ -2179,7 +2179,7 @@ NumPy arrays, and conversion of NumPy integer types to Java boxed types. Transfers to Java ================= -Memory from a NumPy array can be transferred Java in bulk. The transfer of +Memory from a NumPy array can be transferred to Java in bulk. The transfer of a one dimensional NumPy array to Java can either be done at initialization or by use of the Python slice operator. @@ -2271,7 +2271,7 @@ all buffers become invalid and any access to NumPy arrays backed by Java risk crashing. To avoid this fate, either create the memory for the buffer from within Python and pass it to Java. Or use the Java ``java.lang.Runtime.exit`` which will terminate both the Java and Python process without leaving any -opertunity to access a dangling buffer. +opportunity to access a dangling buffer. Buffer backed memory is not limited to use with NumPy. Buffer transfers are supported to provide shared memory between processes or memory mapped files. @@ -2282,7 +2282,7 @@ NumPy Primitives ================ When converting a Python type to a boxed Java type, there is the difficulty -that Java has no way to known the size of a Python numerical value. But when +that Java has no way to know the size of a Python numerical value. But when converting NumPy numerical types, this is not an issue. The following conversions apply to NumPy primitive types. From 8da01ad86b3237346384a5d4d93e5a17d4844524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:12:56 +0000 Subject: [PATCH 11/28] Bump pygments from 2.7.4 to 2.15.0 in /.azure Bumps [pygments](https://github.com/pygments/pygments) from 2.7.4 to 2.15.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.7.4...2.15.0) --- updated-dependencies: - dependency-name: pygments dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .azure/doc-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/doc-requirements.txt b/.azure/doc-requirements.txt index 5b2b51d90..c6e277493 100644 --- a/.azure/doc-requirements.txt +++ b/.azure/doc-requirements.txt @@ -1,6 +1,6 @@ # TODO: consider unpinning these? -Pygments==2.7.4 +Pygments==2.15.0 docutils==0.14 commonmark==0.8.1 recommonmark==0.5.0 From 1be22fcbaa0ec03da338c26c3eb7328bbc90eadd Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 31 Oct 2023 18:07:19 +0100 Subject: [PATCH 12/28] findjvm.py: print() is a function in Python 3 --- examples/linux/findjvm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/linux/findjvm.py b/examples/linux/findjvm.py index 95fe73e0d..1865a3515 100644 --- a/examples/linux/findjvm.py +++ b/examples/linux/findjvm.py @@ -18,4 +18,4 @@ import os.path jvmlib = jpype.getDefaultJVMPath() -print os.path.dirname(os.path.dirname(jvmlib)) +print(os.path.dirname(os.path.dirname(jvmlib))) From db7ccbcc89b9c1f65605e8477998707521bf3fc2 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 31 Oct 2023 19:05:06 +0100 Subject: [PATCH 13/28] .azure/build.yml: Add Python 3.12 to the build --- .azure/build.yml | 6 ++++++ .azure/scripts/test.yml | 1 + 2 files changed, 7 insertions(+) diff --git a/.azure/build.yml b/.azure/build.yml index 4b16e8e04..a4a01b625 100644 --- a/.azure/build.yml +++ b/.azure/build.yml @@ -65,6 +65,9 @@ jobs: linux-3.11: imageName: "ubuntu-latest" python.version: '3.11' + linux-3.12: + imageName: "ubuntu-latest" + python.version: '3.12' windows-3.7: imageName: "windows-2019" python.version: '3.7' @@ -81,6 +84,9 @@ jobs: windows-3.11: imageName: "windows-2019" python.version: '3.11' + windows-3.12: + imageName: "windows-2019" + python.version: '3.12' mac-3.9: imageName: "macos-11" python.version: '3.9' diff --git a/.azure/scripts/test.yml b/.azure/scripts/test.yml index 3177c59ab..c67cdcc2a 100644 --- a/.azure/scripts/test.yml +++ b/.azure/scripts/test.yml @@ -9,6 +9,7 @@ steps: version: 11 - script: | + python -m pip install --upgrade pytest setuptools python setup.py build_ext --inplace displayName: 'Build module' From 88107df0ae5e4886adb6a1ce74ccf768ede7ed66 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 31 Oct 2023 23:40:51 +0100 Subject: [PATCH 14/28] print() is a function in Python 3 --- examples/jms/testJpypePublisher.py | 2 +- examples/jms/testJpypeSubscriber.py | 6 +++--- examples/rmi.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/jms/testJpypePublisher.py b/examples/jms/testJpypePublisher.py index 8e0bf92eb..b658fd4cd 100644 --- a/examples/jms/testJpypePublisher.py +++ b/examples/jms/testJpypePublisher.py @@ -27,7 +27,7 @@ def pyPublisher(javaNamingFactory="weblogic.jndi.WLInitialContextFactory", t0 = time.time() for i in range(NUMMSGS): publisher.publish("Hello World! %s" % i) -print "MessageRate =", float(NUMMSGS) / (time.time() - t0) +print("MessageRate =", float(NUMMSGS) / (time.time() - t0)) # The "Stop" message signals the subscriber to stop timing message receipts publisher.publish("Stop") diff --git a/examples/jms/testJpypeSubscriber.py b/examples/jms/testJpypeSubscriber.py index df682b757..2737df680 100644 --- a/examples/jms/testJpypeSubscriber.py +++ b/examples/jms/testJpypeSubscriber.py @@ -24,12 +24,12 @@ class pyCallback: count = 0 def onMessage(self, text): - print text + print(text) if text == 'Start': pyCallback.startTime = time.time() pyCallback.count = 0 elif text == 'Stop': - print "Message Rate =", float(pyCallback.count) / (time.time() - pyCallback.startTime) + print("Message Rate =", float(pyCallback.count) / (time.time() - pyCallback.startTime)) else: pyCallback.count += 1 @@ -39,7 +39,7 @@ def onMessage(self, text): # Get a subscriber sub = pySubscriber(proxy) -print "Listening..." +print("Listening...") # Prevent this thread from exiting time.sleep(1000) diff --git a/examples/rmi.py b/examples/rmi.py index b9dd38634..4955df8e7 100644 --- a/examples/rmi.py +++ b/examples/rmi.py @@ -25,7 +25,7 @@ p = java.rmi.Naming.lookup("rmi://localhost:2004/server") -print p, p.__class__ +print(p, p.__class__) p.callRemote() From a261138a85f7ba1b5a3db1d0c7be29177ac832d3 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sun, 3 Dec 2023 19:46:37 +0100 Subject: [PATCH 15/28] [doc] config renamed to proper file ending. --- .readthedocs.yml => .readthedocs.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .readthedocs.yml => .readthedocs.yaml (100%) diff --git a/.readthedocs.yml b/.readthedocs.yaml similarity index 100% rename from .readthedocs.yml rename to .readthedocs.yaml From 7c5f31af800dfce1e4ce6f00c03280f586f952a5 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sun, 3 Dec 2023 20:06:31 +0100 Subject: [PATCH 16/28] fix missing os error --- .readthedocs.yaml => .readthedocs.yml | 1 + 1 file changed, 1 insertion(+) rename .readthedocs.yaml => .readthedocs.yml (96%) diff --git a/.readthedocs.yaml b/.readthedocs.yml similarity index 96% rename from .readthedocs.yaml rename to .readthedocs.yml index 4b12f640c..85a378c3b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yml @@ -13,6 +13,7 @@ sphinx: # Optionally set the version of Python and requirements required to build your docs python: + os: ubuntu-20.04 version: "3.8" install: - method: pip From c6c525639493b6de987e66ccb7a03086170584ba Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sun, 3 Dec 2023 20:13:10 +0100 Subject: [PATCH 17/28] f --- .readthedocs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 85a378c3b..2eb5de467 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,8 +13,6 @@ sphinx: # Optionally set the version of Python and requirements required to build your docs python: - os: ubuntu-20.04 - version: "3.8" install: - method: pip path: . @@ -22,7 +20,9 @@ python: - docs build: - image: latest + os: ubuntu-22.04 + python: + version: "3.8" apt_packages: - openjdk-8-jdk From 543c9c74b1aecee04b6f285591388188d3e859a4 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sun, 3 Dec 2023 20:28:31 +0100 Subject: [PATCH 18/28] ff --- .readthedocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 2eb5de467..a8902a067 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -20,9 +20,9 @@ python: - docs build: + tools: python os: ubuntu-22.04 - python: - version: "3.8" + python: 3.8 apt_packages: - openjdk-8-jdk From d50e47066313460f9206f340ffa978eb96c67260 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Sun, 3 Dec 2023 20:30:02 +0100 Subject: [PATCH 19/28] fff --- .readthedocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index a8902a067..253f5707c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -20,9 +20,9 @@ python: - docs build: - tools: python + tools: + python: "3.8" os: ubuntu-22.04 - python: 3.8 apt_packages: - openjdk-8-jdk From 0af425d49954dccfbffbc909edc6f199d8c0a1fd Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 3 Dec 2023 14:36:09 -0800 Subject: [PATCH 20/28] Work on py12 --- native/common/jp_primitivetype.cpp | 12 ++++++++++- native/python/pyjp_char.cpp | 33 +++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index a530f04dd..fbc4f2d0f 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -45,9 +45,19 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) return NULL; ((PyVarObject*) newobj)->ob_size = Py_SIZE(tmp); +#warning PY_VERSION_HEX +#if PY_VERSION_HEX<0x030c0000 + char *p1 = (char*)&(newobj->ob_digit); + char *p2 = (char*)&(tmp->ob_digit); +#else + char *p1 = (char*)&(newobj->long_value); + char *p2 = (char*)&(tmp->long_value); +#endif for (Py_ssize_t i = 0; i < n; i++) { - newobj->ob_digit[i] = tmp->ob_digit[i]; + *p1 = *p2; + p1++; + p2++; } return (PyObject*) newobj; } diff --git a/native/python/pyjp_char.cpp b/native/python/pyjp_char.cpp index 5b4ab19da..2200bcdbb 100644 --- a/native/python/pyjp_char.cpp +++ b/native/python/pyjp_char.cpp @@ -79,46 +79,63 @@ static int assertNotNull(JPValue *javaSlot) PyObject *PyJPChar_Create(PyTypeObject *type, Py_UCS2 p) { + // Allocate a new string type (derived from UNICODE) PyJPChar *self = (PyJPChar*) PyJPValue_alloc(type, 0); if (self == 0) return 0; + + // Set up a wide char with value of zero self->m_Data[0] = 0; self->m_Data[1] = 0; self->m_Data[2] = 0; self->m_Data[3] = 0; + // Values taken from internal/cpython/unicode.h + + // Mark the type in unicode _PyUnicode_LENGTH(self) = 1; _PyUnicode_HASH(self) = -1; - _PyUnicode_STATE(self).kind = PyUnicode_1BYTE_KIND; - _PyUnicode_STATE(self).ascii = 0; - _PyUnicode_STATE(self).ready = 1; - _PyUnicode_STATE(self).interned = 0; _PyUnicode_STATE(self).compact = 1; + _PyUnicode_STATE(self).interned = 0; +#if PY_VERSION_HEX < 0x030c0000 + _PyUnicode_STATE(self).ready = 1; +#endif + + // Copy the value based on the length if (p < 128) { _PyUnicode_STATE(self).ascii = 1; + _PyUnicode_STATE(self).kind = PyUnicode_1BYTE_KIND; + char *data = (char*) (((PyASCIIObject*) self) + 1); data[0] = p; data[1] = 0; - } else - if (p < 256) + } else if (p < 256) { + _PyUnicode_STATE(self).ascii = 0; + _PyUnicode_STATE(self).kind = PyUnicode_1BYTE_KIND; + char *data = (char*) ( ((PyCompactUnicodeObject*) self) + 1); data[0] = p; data[1] = 0; + +#if PY_VERSION_HEX < 0x030c0000 _PyUnicode_WSTR_LENGTH(self) = 0; _PyUnicode_WSTR(self) = NULL; +#endif self->m_Obj.utf8 = NULL; self->m_Obj.utf8_length = 0; } else { + _PyUnicode_STATE(self).ascii = 0; + _PyUnicode_STATE(self).kind = PyUnicode_2BYTE_KIND; Py_UCS2 *data = (Py_UCS2*) ( ((PyCompactUnicodeObject*) self) + 1); data[0] = p; data[1] = 0; - _PyUnicode_STATE(self).kind = PyUnicode_2BYTE_KIND; +#if PY_VERSION_HEX < 0x030c0000 if (sizeof (wchar_t) == 2) { _PyUnicode_WSTR_LENGTH(self) = 1; @@ -128,9 +145,11 @@ PyObject *PyJPChar_Create(PyTypeObject *type, Py_UCS2 p) _PyUnicode_WSTR_LENGTH(self) = 0; _PyUnicode_WSTR(self) = NULL; } +#endif self->m_Obj.utf8 = NULL; self->m_Obj.utf8_length = 0; } + return (PyObject*) self; } From 5c49b8e15262e53efd0fe4f2d93f18322b54bd38 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 3 Dec 2023 15:21:48 -0800 Subject: [PATCH 21/28] Work of starting jvm --- native/common/jp_primitivetype.cpp | 14 +++++++++----- native/python/pyjp_module.cpp | 10 ++++++++++ test/jpypetest/test_javadoc.py | 4 +++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index fbc4f2d0f..616faa44e 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -36,6 +36,8 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) { if (wrapper == NULL) JP_RAISE(PyExc_SystemError, "bad wrapper"); + + // Determine number of bytes to copy Py_ssize_t n = Py_SIZE(tmp); if (n < 0) n = -n; @@ -44,14 +46,16 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) if (newobj == NULL) return NULL; + // Size is in units of digits + ((PyVarObject*) newobj)->ob_size = Py_SIZE(tmp); -#warning PY_VERSION_HEX #if PY_VERSION_HEX<0x030c0000 - char *p1 = (char*)&(newobj->ob_digit); - char *p2 = (char*)&(tmp->ob_digit); + digit *p1 = (digit*)&(newobj->ob_digit); + digit *p2 = (digit*)&(tmp->ob_digit); #else - char *p1 = (char*)&(newobj->long_value); - char *p2 = (char*)&(tmp->long_value); + newobj->long_value.lv_tag = tmp->long_value.lv_tag; + digit *p1 = (digit*)&(newobj->long_value.ob_digit); + digit *p2 = (digit*)&(tmp->long_value.ob_digit); #endif for (Py_ssize_t i = 0; i < n; i++) { diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index 9dcfdfdf5..695bc5c70 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -169,11 +169,21 @@ PyObject* PyJP_GetAttrDescriptor(PyTypeObject *type, PyObject *attr_name) if (type->tp_mro == NULL) return NULL; // GCOVR_EXCL_LINE + // Grab the mro PyObject *mro = type->tp_mro; + + // mco should be a tuple Py_ssize_t n = PyTuple_Size(mro); + + // Search the tuple for the attribute for (Py_ssize_t i = 0; i < n; ++i) { PyTypeObject *type2 = (PyTypeObject*) PyTuple_GetItem(mro, i); + + // Skip objects without a functioning dictionary + if (type2->tp_dict == NULL) + continue; + PyObject *res = PyDict_GetItem(type2->tp_dict, attr_name); if (res) { diff --git a/test/jpypetest/test_javadoc.py b/test/jpypetest/test_javadoc.py index 0f5f93171..a17f11d03 100644 --- a/test/jpypetest/test_javadoc.py +++ b/test/jpypetest/test_javadoc.py @@ -38,7 +38,9 @@ def testClass(self): JC = jpype.JClass("jpype.doc.Test") jd = JC.__doc__ self.assertIsInstance(jd, str) - self.assertRegex(jd, "random stuff") + # Disabled this test for now. Java needs a better API for accessing Java doc. + # It is hard to deal with random changes every version. + #self.assertRegex(jd, "random stuff") def testMethod(self): JC = jpype.JClass("jpype.doc.Test") From 485de7f0b980a27f19b9b7cf9b17a1adaf177d8c Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 3 Dec 2023 20:45:58 -0800 Subject: [PATCH 22/28] Work on int types --- native/common/jp_primitivetype.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index 616faa44e..343a4a621 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -46,23 +46,23 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) if (newobj == NULL) return NULL; - // Size is in units of digits - ((PyVarObject*) newobj)->ob_size = Py_SIZE(tmp); #if PY_VERSION_HEX<0x030c0000 digit *p1 = (digit*)&(newobj->ob_digit); digit *p2 = (digit*)&(tmp->ob_digit); -#else - newobj->long_value.lv_tag = tmp->long_value.lv_tag; - digit *p1 = (digit*)&(newobj->long_value.ob_digit); - digit *p2 = (digit*)&(tmp->long_value.ob_digit); -#endif + + // Size is in units of digits for (Py_ssize_t i = 0; i < n; i++) { *p1 = *p2; p1++; p2++; } + +#else + // Size is in units of bytes + tag size + memcpy(&newobj->long_value, &tmp->long_value, n+sizeof(uintptr_t)); +#endif return (PyObject*) newobj; } From 737ae0e5d3987be409ae656bcc7e7be8db029b7c Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 3 Dec 2023 22:58:36 -0800 Subject: [PATCH 23/28] Fix broken PyLong... no more ob_size so now need lots of kludges. --- native/common/jp_primitivetype.cpp | 26 +++++++++++++++----------- native/python/pyjp_value.cpp | 12 ++++++++++-- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index 343a4a621..499ae3da4 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -29,6 +29,7 @@ bool JPPrimitiveType::isPrimitive() const return true; } +extern "C" Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self); // equivalent of long_subtype_new as it isn't exposed @@ -37,7 +38,8 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) if (wrapper == NULL) JP_RAISE(PyExc_SystemError, "bad wrapper"); - // Determine number of bytes to copy +#if PY_VERSION_HEX<0x030c0000 + // Determine number of digits to copy Py_ssize_t n = Py_SIZE(tmp); if (n < 0) n = -n; @@ -46,22 +48,24 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) if (newobj == NULL) return NULL; - ((PyVarObject*) newobj)->ob_size = Py_SIZE(tmp); -#if PY_VERSION_HEX<0x030c0000 - digit *p1 = (digit*)&(newobj->ob_digit); - digit *p2 = (digit*)&(tmp->ob_digit); - // Size is in units of digits for (Py_ssize_t i = 0; i < n; i++) { - *p1 = *p2; - p1++; - p2++; + newobj->ob_digit[i] = tmp->ob_digit[i]; } #else - // Size is in units of bytes + tag size - memcpy(&newobj->long_value, &tmp->long_value, n+sizeof(uintptr_t)); + // 3.12 completely does away with ob_size field and repurposes it + + // Determine the number of digits to copy + int n = (tmp->long_value.lv_tag >> 3); + + PyLongObject *newobj = (PyLongObject *) wrapper->tp_alloc(wrapper, n); + if (newobj == NULL) + return NULL; + + newobj->long_value.lv_tag = tmp->long_value.lv_tag; + memcpy(&newobj->long_value.ob_digit, &tmp->long_value.ob_digit, n*sizeof(digit)); #endif return (PyObject*) newobj; } diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 93dba9505..140f1bbc3 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -69,7 +69,10 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems ) return PyErr_NoMemory(); // GCOVR_EXCL_LINE memset(obj, 0, size); + Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; + obj->ob_type = type; + if (type->tp_itemsize == 0) PyObject_Init(obj, type); else @@ -107,8 +110,13 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) || type->tp_finalize != (destructor) PyJPValue_finalize) return 0; Py_ssize_t offset; - Py_ssize_t sz = Py_SIZE(self); - // I have no clue what negative sizes mean + Py_ssize_t sz = 0; + // starting in 3.12 there is no longer ob_size in PyLong + if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) + sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS + else if (type->tp_itemsize != 0) + sz = Py_SIZE(self); + // PyLong abuses ob_size with negative values prior to 3.12 if (sz < 0) sz = -sz; if (type->tp_itemsize == 0) From 9f4a286b844c2c45a96dfda95616c01976288ff5 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Mon, 4 Dec 2023 19:42:22 -0800 Subject: [PATCH 24/28] Works with py12 --- native/python/pyjp_array.cpp | 18 ++++++------------ native/python/pyjp_buffer.cpp | 7 ++----- native/python/pyjp_class.cpp | 6 ++++++ test/jpypetest/test_bytebuffer.py | 2 +- test/jpypetest/test_classhints.py | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/native/python/pyjp_array.cpp b/native/python/pyjp_array.cpp index 9dc53774c..f3e100e29 100644 --- a/native/python/pyjp_array.cpp +++ b/native/python/pyjp_array.cpp @@ -428,14 +428,11 @@ static PyType_Slot arraySlots[] = { { Py_sq_length, (void*) &PyJPArray_len}, { Py_tp_getset, (void*) &arrayGetSets}, { Py_mp_ass_subscript, (void*) &PyJPArray_assignSubscript}, + { Py_bf_getbuffer, (void*) &PyJPArray_getBuffer}, + { Py_bf_releasebuffer, (void*) &PyJPArray_releaseBuffer}, {0} }; -static PyBufferProcs arrayBuffer = { - (getbufferproc) & PyJPArray_getBuffer, - (releasebufferproc) & PyJPArray_releaseBuffer -}; - PyTypeObject *PyJPArray_Type = NULL; static PyType_Spec arraySpec = { "_jpype._JArray", @@ -445,12 +442,9 @@ static PyType_Spec arraySpec = { arraySlots }; -static PyBufferProcs arrayPrimBuffer = { - (getbufferproc) & PyJPArrayPrimitive_getBuffer, - (releasebufferproc) & PyJPArray_releaseBuffer -}; - static PyType_Slot arrayPrimSlots[] = { + { Py_bf_getbuffer, (void*) &PyJPArrayPrimitive_getBuffer}, + { Py_bf_releasebuffer, (void*) &PyJPArray_releaseBuffer}, {0} }; @@ -472,14 +466,14 @@ void PyJPArray_initType(PyObject * module) JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type)); PyJPArray_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&arraySpec, tuple.get()); JP_PY_CHECK(); - PyJPArray_Type->tp_as_buffer = &arrayBuffer; + //PyJPArray_Type->tp_as_buffer = &arrayBuffer; PyModule_AddObject(module, "_JArray", (PyObject*) PyJPArray_Type); JP_PY_CHECK(); tuple = JPPyObject::call(PyTuple_Pack(1, PyJPArray_Type)); PyJPArrayPrimitive_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&arrayPrimSpec, tuple.get()); - PyJPArrayPrimitive_Type->tp_as_buffer = &arrayPrimBuffer; + //PyJPArrayPrimitive_Type->tp_as_buffer = &arrayPrimBuffer; JP_PY_CHECK(); PyModule_AddObject(module, "_JArrayPrimitive", (PyObject*) PyJPArrayPrimitive_Type); diff --git a/native/python/pyjp_buffer.cpp b/native/python/pyjp_buffer.cpp index e2f56e1f6..a3c58d418 100644 --- a/native/python/pyjp_buffer.cpp +++ b/native/python/pyjp_buffer.cpp @@ -113,13 +113,11 @@ int PyJPBuffer_getBuffer(PyJPBuffer *self, Py_buffer *view, int flags) static PyType_Slot bufferSlots[] = { { Py_tp_dealloc, (void*) PyJPBuffer_dealloc}, { Py_tp_repr, (void*) PyJPBuffer_repr}, + { Py_bf_getbuffer, (void*) PyJPBuffer_getBuffer}, + { Py_bf_releasebuffer, (void*) PyJPBuffer_releaseBuffer}, {0} }; -static PyBufferProcs directBuffer = { - (getbufferproc) & PyJPBuffer_getBuffer, - (releasebufferproc) & PyJPBuffer_releaseBuffer -}; PyTypeObject *PyJPBuffer_Type = NULL; static PyType_Spec bufferSpec = { @@ -138,7 +136,6 @@ void PyJPBuffer_initType(PyObject * module) { JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type)); PyJPBuffer_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&bufferSpec, tuple.get()); - PyJPBuffer_Type->tp_as_buffer = &directBuffer; JP_PY_CHECK(); PyModule_AddObject(module, "_JBuffer", (PyObject*) PyJPBuffer_Type); JP_PY_CHECK(); diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index 0fb283959..b4ea05809 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -276,6 +276,12 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) case Py_tp_getset: type->tp_getset = (PyGetSetDef*) slot->pfunc; break; + case Py_bf_getbuffer: + type->tp_as_buffer->bf_getbuffer = (getbufferproc) slot->pfunc; + break; + case Py_bf_releasebuffer: + type->tp_as_buffer->bf_releasebuffer = (releasebufferproc) slot->pfunc; + break; // GCOVR_EXCL_START default: PyErr_Format(PyExc_TypeError, "slot %d not implemented", slot->slot); diff --git a/test/jpypetest/test_bytebuffer.py b/test/jpypetest/test_bytebuffer.py index b4c7e4757..d81ed3dd8 100644 --- a/test/jpypetest/test_bytebuffer.py +++ b/test/jpypetest/test_bytebuffer.py @@ -48,4 +48,4 @@ def testRepr(self): self.assertEqual(repr(bb), "") def testMemoryView(self): - self.assertEquals(memoryview(jpype.java.nio.ByteBuffer.allocateDirect(100)).nbytes, 100) + self.assertEqual(memoryview(jpype.java.nio.ByteBuffer.allocateDirect(100)).nbytes, 100) diff --git a/test/jpypetest/test_classhints.py b/test/jpypetest/test_classhints.py index bcf895622..08c1187a9 100644 --- a/test/jpypetest/test_classhints.py +++ b/test/jpypetest/test_classhints.py @@ -59,7 +59,7 @@ def testCharSequence(self): def testInstant(self): import datetime - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.UTC) Instant = jpype.JClass("java.time.Instant") self.assertIsInstance(jpype.JObject(now, Instant), Instant) From c61d75c2957f32fb349b40445fc2b1d5bdd9d5e0 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Mon, 4 Dec 2023 19:54:55 -0800 Subject: [PATCH 25/28] Runs on Python 3.10 --- native/common/jp_primitivetype.cpp | 1 + native/python/pyjp_value.cpp | 6 +++++- test/jpypetest/test_classhints.py | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/native/common/jp_primitivetype.cpp b/native/common/jp_primitivetype.cpp index 499ae3da4..a07975a86 100644 --- a/native/common/jp_primitivetype.cpp +++ b/native/common/jp_primitivetype.cpp @@ -49,6 +49,7 @@ PyObject *JPPrimitiveType::convertLong(PyTypeObject* wrapper, PyLongObject* tmp) return NULL; // Size is in units of digits + ((PyVarObject*) newobj)->ob_size = Py_SIZE(tmp); for (Py_ssize_t i = 0; i < n; i++) { newobj->ob_digit[i] = tmp->ob_digit[i]; diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 140f1bbc3..4546c6d36 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -111,10 +111,14 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) return 0; Py_ssize_t offset; Py_ssize_t sz = 0; + +#if PY_VERSION_HEX>=0x030c0000 // starting in 3.12 there is no longer ob_size in PyLong if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS - else if (type->tp_itemsize != 0) + else +#endif + if (type->tp_itemsize != 0) sz = Py_SIZE(self); // PyLong abuses ob_size with negative values prior to 3.12 if (sz < 0) diff --git a/test/jpypetest/test_classhints.py b/test/jpypetest/test_classhints.py index 08c1187a9..d200d798a 100644 --- a/test/jpypetest/test_classhints.py +++ b/test/jpypetest/test_classhints.py @@ -17,6 +17,7 @@ # ***************************************************************************** import jpype import common +import sys class MyImpl(object): @@ -59,7 +60,10 @@ def testCharSequence(self): def testInstant(self): import datetime - now = datetime.datetime.now(datetime.UTC) + if sys.version_info.major == 3 and sys.version_info.minor < 12: + now = datetime.datetime.utcnow() + else: + now = datetime.datetime.now(datetime.UTC) Instant = jpype.JClass("java.time.Instant") self.assertIsInstance(jpype.JObject(now, Instant), Instant) From c28a6a66bbb350060ebd5514ead6e3a0aac52fe1 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Mon, 4 Dec 2023 20:05:51 -0800 Subject: [PATCH 26/28] Remove deprecated call --- native/common/jp_gc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/common/jp_gc.cpp b/native/common/jp_gc.cpp index 8b711f997..795300788 100644 --- a/native/common/jp_gc.cpp +++ b/native/common/jp_gc.cpp @@ -85,8 +85,8 @@ size_t getWorkingSize() return sz * page_size; #elif defined(USE_MALLINFO) - struct mallinfo mi; - mi = mallinfo(); + struct mallinfo2 mi; + mi = mallinfo2(); current = (size_t) mi.uordblks; #endif From 5343c950c4d4c7d93f4d81402fc0ea835cf447c9 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Mon, 4 Dec 2023 22:36:37 -0800 Subject: [PATCH 27/28] Works for 3.9 --- native/python/pyjp_array.cpp | 26 ++++++++++++++++++++++++-- native/python/pyjp_buffer.cpp | 11 +++++++++++ native/python/pyjp_class.cpp | 2 ++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/native/python/pyjp_array.cpp b/native/python/pyjp_array.cpp index f3e100e29..17bba6292 100644 --- a/native/python/pyjp_array.cpp +++ b/native/python/pyjp_array.cpp @@ -428,11 +428,20 @@ static PyType_Slot arraySlots[] = { { Py_sq_length, (void*) &PyJPArray_len}, { Py_tp_getset, (void*) &arrayGetSets}, { Py_mp_ass_subscript, (void*) &PyJPArray_assignSubscript}, +#if PY_VERSION_HEX >= 0x03090000 { Py_bf_getbuffer, (void*) &PyJPArray_getBuffer}, { Py_bf_releasebuffer, (void*) &PyJPArray_releaseBuffer}, +#endif {0} }; +#if PY_VERSION_HEX < 0x03090000 +static PyBufferProcs arrayBuffer = { + (getbufferproc) & PyJPArray_getBuffer, + (releasebufferproc) & PyJPArray_releaseBuffer +}; +#endif + PyTypeObject *PyJPArray_Type = NULL; static PyType_Spec arraySpec = { "_jpype._JArray", @@ -442,9 +451,18 @@ static PyType_Spec arraySpec = { arraySlots }; +#if PY_VERSION_HEX < 0x03090000 +static PyBufferProcs arrayPrimBuffer = { + (getbufferproc) & PyJPArrayPrimitive_getBuffer, + (releasebufferproc) & PyJPArray_releaseBuffer +}; +#endif + static PyType_Slot arrayPrimSlots[] = { +#if PY_VERSION_HEX >= 0x03090000 { Py_bf_getbuffer, (void*) &PyJPArrayPrimitive_getBuffer}, { Py_bf_releasebuffer, (void*) &PyJPArray_releaseBuffer}, +#endif {0} }; @@ -466,14 +484,18 @@ void PyJPArray_initType(PyObject * module) JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type)); PyJPArray_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&arraySpec, tuple.get()); JP_PY_CHECK(); - //PyJPArray_Type->tp_as_buffer = &arrayBuffer; +#if PY_VERSION_HEX < 0x03090000 + PyJPArray_Type->tp_as_buffer = &arrayBuffer; +#endif PyModule_AddObject(module, "_JArray", (PyObject*) PyJPArray_Type); JP_PY_CHECK(); tuple = JPPyObject::call(PyTuple_Pack(1, PyJPArray_Type)); PyJPArrayPrimitive_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&arrayPrimSpec, tuple.get()); - //PyJPArrayPrimitive_Type->tp_as_buffer = &arrayPrimBuffer; +#if PY_VERSION_HEX < 0x03090000 + PyJPArrayPrimitive_Type->tp_as_buffer = &arrayPrimBuffer; +#endif JP_PY_CHECK(); PyModule_AddObject(module, "_JArrayPrimitive", (PyObject*) PyJPArrayPrimitive_Type); diff --git a/native/python/pyjp_buffer.cpp b/native/python/pyjp_buffer.cpp index a3c58d418..0dc95ac11 100644 --- a/native/python/pyjp_buffer.cpp +++ b/native/python/pyjp_buffer.cpp @@ -113,11 +113,19 @@ int PyJPBuffer_getBuffer(PyJPBuffer *self, Py_buffer *view, int flags) static PyType_Slot bufferSlots[] = { { Py_tp_dealloc, (void*) PyJPBuffer_dealloc}, { Py_tp_repr, (void*) PyJPBuffer_repr}, +#if PY_VERSION_HEX >= 0x03090000 { Py_bf_getbuffer, (void*) PyJPBuffer_getBuffer}, { Py_bf_releasebuffer, (void*) PyJPBuffer_releaseBuffer}, +#endif {0} }; +#if PY_VERSION_HEX < 0x03090000 +static PyBufferProcs directBuffer = { + (getbufferproc) & PyJPBuffer_getBuffer, + (releasebufferproc) & PyJPBuffer_releaseBuffer +}; +#endif PyTypeObject *PyJPBuffer_Type = NULL; static PyType_Spec bufferSpec = { @@ -136,6 +144,9 @@ void PyJPBuffer_initType(PyObject * module) { JPPyObject tuple = JPPyObject::call(PyTuple_Pack(1, PyJPObject_Type)); PyJPBuffer_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&bufferSpec, tuple.get()); +#if PY_VERSION_HEX < 0x03090000 + PyJPBuffer_Type->tp_as_buffer = &directBuffer; +#endif JP_PY_CHECK(); PyModule_AddObject(module, "_JBuffer", (PyObject*) PyJPBuffer_Type); JP_PY_CHECK(); diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index b4ea05809..dc6f28229 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -276,12 +276,14 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) case Py_tp_getset: type->tp_getset = (PyGetSetDef*) slot->pfunc; break; +#if PY_VERSION_HEX >= 0x03090000 case Py_bf_getbuffer: type->tp_as_buffer->bf_getbuffer = (getbufferproc) slot->pfunc; break; case Py_bf_releasebuffer: type->tp_as_buffer->bf_releasebuffer = (releasebufferproc) slot->pfunc; break; +#endif // GCOVR_EXCL_START default: PyErr_Format(PyExc_TypeError, "slot %d not implemented", slot->slot); From 2e15660f15ae0a5f29d451fffca6d8d522e22036 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Tue, 5 Dec 2023 08:43:51 -0800 Subject: [PATCH 28/28] Typo --- native/python/pyjp_module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index 695bc5c70..d0d42cc10 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -172,7 +172,7 @@ PyObject* PyJP_GetAttrDescriptor(PyTypeObject *type, PyObject *attr_name) // Grab the mro PyObject *mro = type->tp_mro; - // mco should be a tuple + // mro should be a tuple Py_ssize_t n = PyTuple_Size(mro); // Search the tuple for the attribute