From d6dbda28941d06dc2dacbc94ded4ef19aee8e72e Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 20 Apr 2024 14:19:00 -0700 Subject: [PATCH 01/11] Fixes #1181 --- doc/CHANGELOG.rst | 3 ++ native/common/include/jp_context.h | 1 - native/common/include/jp_exception.h | 3 +- native/common/include/jpype.h | 3 +- native/common/jp_context.cpp | 1 - native/common/jp_exception.cpp | 12 ----- native/common/jp_proxy.cpp | 9 ++-- native/java/org/jpype/proxy/JPypeProxy.java | 57 ++++++++++++-------- test/harness/jpype/proxy/TestInterface1.java | 2 + test/jpypetest/test_proxy.py | 30 +++++++++++ 10 files changed, 75 insertions(+), 46 deletions(-) diff --git a/doc/CHANGELOG.rst b/doc/CHANGELOG.rst index 74451c308..666be74dd 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.5.1_dev0 - 2023-12-15** + + - Allow access to default methods implemented in interfaces when using ``@JImplements``. + - Use PEP-518 and PEP-660 configuration for the package, allowing editable and configurable builds using modern Python packaging tooling. Where before ``python setup.py --enable-tracing develop``, now can be done with diff --git a/native/common/include/jp_context.h b/native/common/include/jp_context.h index d5cd4c4b9..0b357f786 100644 --- a/native/common/include/jp_context.h +++ b/native/common/include/jp_context.h @@ -220,7 +220,6 @@ class JPContext public: JPClassRef m_ContextClass; JPClassRef m_RuntimeException; - JPClassRef m_NoSuchMethodError; private: JPClassRef m_Array; diff --git a/native/common/include/jp_exception.h b/native/common/include/jp_exception.h index ad9596366..f23b9dea6 100644 --- a/native/common/include/jp_exception.h +++ b/native/common/include/jp_exception.h @@ -58,7 +58,6 @@ _python_error, _python_exc, _os_error_unix, _os_error_windows, -_method_not_found, }; // Create a stackinfo for a particular location in the code that can then @@ -160,4 +159,4 @@ class JPypeException : std::runtime_error JPThrowableRef m_Throwable; }; -#endif \ No newline at end of file +#endif diff --git a/native/common/include/jpype.h b/native/common/include/jpype.h index 406e4b67f..62aa4fe68 100644 --- a/native/common/include/jpype.h +++ b/native/common/include/jpype.h @@ -168,7 +168,6 @@ class JPResource #define JP_RAISE_PYTHON() { throw JPypeException(JPError::_python_error, nullptr, JP_STACKINFO()); } #define JP_RAISE_OS_ERROR_UNIX(err, msg) { throw JPypeException(JPError::_os_error_unix, msg, err, JP_STACKINFO()); } #define JP_RAISE_OS_ERROR_WINDOWS(err, msg) { throw JPypeException(JPError::_os_error_windows, msg, err, JP_STACKINFO()); } -#define JP_RAISE_METHOD_NOT_FOUND(msg) { throw JPypeException(JPError::_method_not_found, nullptr, msg, JP_STACKINFO()); } #define JP_RAISE(type, msg) { throw JPypeException(JPError::_python_exc, type, msg, JP_STACKINFO()); } #ifndef PyObject_HEAD @@ -196,4 +195,4 @@ using PyObject = _object; // Primitives classes #include "jp_primitivetype.h" -#endif // _JPYPE_H_ \ No newline at end of file +#endif // _JPYPE_H_ diff --git a/native/common/jp_context.cpp b/native/common/jp_context.cpp index fc2baa8d2..d6e25cb2f 100644 --- a/native/common/jp_context.cpp +++ b/native/common/jp_context.cpp @@ -181,7 +181,6 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt) m_Object_HashCodeID = frame.GetMethodID(objectClass, "hashCode", "()I"); m_Object_GetClassID = frame.GetMethodID(objectClass, "getClass", "()Ljava/lang/Class;"); - m_NoSuchMethodError = JPClassRef(frame, (jclass) frame.FindClass("java/lang/NoSuchMethodError")); m_RuntimeException = JPClassRef(frame, (jclass) frame.FindClass("java/lang/RuntimeException")); jclass stringClass = frame.FindClass("java/lang/String"); diff --git a/native/common/jp_exception.cpp b/native/common/jp_exception.cpp index 10cc9c9ec..5e300cf6f 100644 --- a/native/common/jp_exception.cpp +++ b/native/common/jp_exception.cpp @@ -303,12 +303,6 @@ void JPypeException::toPython() } else if (m_Type == JPError::_python_error) { // Already on the stack - } else if (m_Type == JPError::_method_not_found) - { - // This is hit when a proxy fails to implement a required - // method. Only older style proxies should be able hit this. - JP_TRACE("Runtime error"); - PyErr_SetString(PyExc_RuntimeError, mesg); }// This section is only reachable during startup of the JVM. // GCOVR_EXCL_START else if (m_Type == JPError::_os_error_unix) @@ -428,12 +422,6 @@ void JPypeException::toJava(JPContext *context) return; } - if (m_Type == JPError::_method_not_found) - { - frame.ThrowNew(context->m_NoSuchMethodError.get(), mesg); - return; - } - if (m_Type == JPError::_python_error) { JPPyCallAcquire callback; diff --git a/native/common/jp_proxy.cpp b/native/common/jp_proxy.cpp index 8ad3f464d..432554e2b 100644 --- a/native/common/jp_proxy.cpp +++ b/native/common/jp_proxy.cpp @@ -52,7 +52,8 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( jlong hostObj, jlong returnTypePtr, jlongArray parameterTypePtrs, - jobjectArray args) + jobjectArray args, + jobject missing) { auto* context = (JPContext*) contextPtr; JPJavaFrame frame = JPJavaFrame::external(context, env); @@ -84,11 +85,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke( // If method can't be called, throw an exception if (callable.isNull() || callable.get() == Py_None) - { - JP_TRACE("Callable not found"); - JP_RAISE_METHOD_NOT_FOUND(cname); - return nullptr; - } + return missing; // Find the return type auto* returnClass = (JPClass*) returnTypePtr; diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/org/jpype/proxy/JPypeProxy.java index f2c8dd445..aae264ec4 100644 --- a/native/java/org/jpype/proxy/JPypeProxy.java +++ b/native/java/org/jpype/proxy/JPypeProxy.java @@ -15,6 +15,7 @@ **************************************************************************** */ package org.jpype.proxy; +import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -35,6 +36,7 @@ public class JPypeProxy implements InvocationHandler public long cleanup; Class[] interfaces; ClassLoader cl = ClassLoader.getSystemClassLoader(); + public static Object missing = new Object(); public static JPypeProxy newProxy(JPypeContext context, long instance, @@ -69,35 +71,46 @@ public Object newInstance() public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - try - { -// context.incrementProxy(); - if (context.isShutdown()) - throw new RuntimeException("Proxy called during shutdown"); - // We can save a lot of effort on the C++ side by doing all the - // type lookup work here. - TypeManager typeManager = context.getTypeManager(); - long returnType; - long[] parameterTypes; - synchronized (typeManager) + if (context.isShutdown()) + throw new RuntimeException("Proxy called during shutdown"); + + // We can save a lot of effort on the C++ side by doing all the + // type lookup work here. + TypeManager typeManager = context.getTypeManager(); + long returnType; + long[] parameterTypes; + synchronized (typeManager) + { + returnType = typeManager.findClass(method.getReturnType()); + Class[] types = method.getParameterTypes(); + parameterTypes = new long[types.length]; + for (int i = 0; i < types.length; ++i) { - returnType = typeManager.findClass(method.getReturnType()); - Class[] types = method.getParameterTypes(); - parameterTypes = new long[types.length]; - for (int i = 0; i < types.length; ++i) - { - parameterTypes[i] = typeManager.findClass(types[i]); - } + parameterTypes[i] = typeManager.findClass(types[i]); } + } - return hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args); - } finally + // Check first to see if Python has implementated it + Object result = hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args, missing); + + // If we get a good result than return it + if (result != missing) + return result; + + // If it is a default method in the interface then we have to invoke it using special reflection. + if (method.isDefault()) { -// context.decrementProxy(); + return MethodHandles.lookup() + .unreflectSpecial(method, method.getDeclaringClass()) + .bindTo(proxy) + .invokeWithArguments(args); } + + // Else throw... (this should never happen as proxies are checked when created.) + throw new NoSuchMethodError(method.getName()); } private static native Object hostInvoke(long context, String name, long pyObject, - long returnType, long[] argsTypes, Object[] args); + long returnType, long[] argsTypes, Object[] args, Object bad); } diff --git a/test/harness/jpype/proxy/TestInterface1.java b/test/harness/jpype/proxy/TestInterface1.java index d9ed797ea..00985e31c 100644 --- a/test/harness/jpype/proxy/TestInterface1.java +++ b/test/harness/jpype/proxy/TestInterface1.java @@ -19,4 +19,6 @@ public interface TestInterface1 { int testMethod1(); + + default int testDefault() { return 1234; } } diff --git a/test/jpypetest/test_proxy.py b/test/jpypetest/test_proxy.py index 0cba57b52..87dbe67bd 100644 --- a/test/jpypetest/test_proxy.py +++ b/test/jpypetest/test_proxy.py @@ -135,6 +135,34 @@ class MyImpl(object): def testMethod1(self): pass + def testDefault1(self): + itf1 = self.package.TestInterface1 + + @JImplements(itf1) + class MyImpl(object): + @JOverride + def testMethod1(self): + pass + + obj = itf1@MyImpl() + self.assertEqual(obj.testDefault(), 1234) + + def testDefault2(self): + itf1 = self.package.TestInterface1 + + @JImplements(itf1) + class MyImpl(object): + @JOverride + def testMethod1(self): + pass + + @JOverride + def testDefault(self): + return 5678 + + obj = itf1@MyImpl() + self.assertEqual(obj.testDefault(), 5678) + def testProxyImplementsForm2(self): itf1 = self.package.TestInterface1 itf2 = self.package.TestInterface2 @@ -560,3 +588,5 @@ def run(self): startJVM() assert isinstance(MyImpl(), MyImpl) + + From 902156a91d7d38493d6f2d011f26e6267a4eac51 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 23 Jun 2024 13:51:41 -0700 Subject: [PATCH 02/11] Another attempt using an agent --- jpype/_core.py | 5 ++ native/common/jp_classloader.cpp | 58 +------------------ native/java/manifest.txt | 2 + native/java/org/jpype/agent/JPypeAgent.java | 10 ++++ project/jpype_java/manifest.mf | 2 + .../jpype_java/nbproject/project.properties | 1 + setupext/build_ext.py | 6 +- 7 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 native/java/manifest.txt create mode 100644 native/java/org/jpype/agent/JPypeAgent.java create mode 100644 project/jpype_java/manifest.mf diff --git a/jpype/_core.py b/jpype/_core.py index ca1191d01..aa7f2d06e 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -237,6 +237,11 @@ def startJVM( if classpath: extra_jvm_args += (f'-Djava.class.path={_handleClassPath(classpath)}', ) + supportLib = os.path.join(os.path.dirname(os.path.dirname(__file__)),"org.jpype.jar") + if not os.path.exists(supportLib): + raise RuntimeError("Unable to find org.jpype.jar support library at "+supportLib) + extra_jvm_args += ('-javaagent:'+supportLib,) + try: import locale # Gather a list of locale settings that Java may override (excluding LC_ALL) diff --git a/native/common/jp_classloader.cpp b/native/common/jp_classloader.cpp index 9e754fb8d..78023283b 100644 --- a/native/common/jp_classloader.cpp +++ b/native/common/jp_classloader.cpp @@ -17,29 +17,13 @@ #include #include #include +#include jobject JPClassLoader::getBootLoader() { return m_BootLoader.get(); } -static jobject toURL(JPJavaFrame &frame, const string& path) -{ - // file = new File("org.jpype.jar"); - jclass fileClass = frame.FindClass("java/io/File"); - jmethodID newFile = frame.GetMethodID(fileClass, "", "(Ljava/lang/String;)V"); - jvalue v[3]; - v[0].l = frame.NewStringUTF(path.c_str()); - jobject file = frame.NewObjectA(fileClass, newFile, v); - - // url = file.toURI().toURL(); - jmethodID toURI = frame.GetMethodID(fileClass, "toURI", "()Ljava/net/URI;"); - jobject uri = frame.CallObjectMethodA(file, toURI, nullptr); - jclass uriClass = frame.GetObjectClass(uri); - jmethodID toURL = frame.GetMethodID(uriClass, "toURL", "()Ljava/net/URL;"); - return frame.CallObjectMethodA(uri, toURL, nullptr); -} - JPClassLoader::JPClassLoader(JPJavaFrame& frame) { JP_TRACE_IN("JPClassLoader::JPClassLoader"); @@ -68,44 +52,8 @@ JPClassLoader::JPClassLoader(JPJavaFrame& frame) } frame.ExceptionClear(); - // Harder, we need to find the _jpype module and use __file__ to obtain a - // path. - JPPyObject pypath = JPPyObject::call(PyObject_GetAttrString(PyJPModule, "__file__")); - string path = JPPyString::asStringUTF8(pypath.get()); - string::size_type i = path.find_last_of('\\'); - if (i == string::npos) - i = path.find_last_of('/'); - if (i == string::npos) - JP_RAISE(PyExc_RuntimeError, "Can't find jar path"); - path = path.substr(0, i + 1); - jobject url1 = toURL(frame, path + "org.jpype.jar"); - // jobject url2 = toURL(frame, path + "lib/asm-8.0.1.jar"); - - // urlArray = new URL[]{url}; - jclass urlClass = frame.GetObjectClass(url1); - jobjectArray urlArray = frame.NewObjectArray(1, urlClass, nullptr); - frame.SetObjectArrayElement(urlArray, 0, url1); - // frame.SetObjectArrayElement(urlArray, 1, url2); - - // cl = new URLClassLoader(urlArray); - jclass urlLoaderClass = frame.FindClass("java/net/URLClassLoader"); - jmethodID newURLClassLoader = frame.GetMethodID(urlLoaderClass, "", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); - jvalue v[3]; - v[0].l = (jobject) urlArray; - v[1].l = (jobject) m_SystemClassLoader.get(); - jobject cl = frame.NewObjectA(urlLoaderClass, newURLClassLoader, v); - - // Class dycl = Class.forName("org.jpype.classloader.DynamicClassLoader", true, cl); - v[0].l = frame.NewStringUTF("org.jpype.classloader.DynamicClassLoader"); - v[1].z = true; - v[2].l = cl; - auto dyClass = (jclass) frame.CallStaticObjectMethodA(m_ClassClass.get(), m_ForNameID, v); - - // dycl.newInstance(systemClassLoader); - jmethodID newDyLoader = frame.GetMethodID(dyClass, "", "(Ljava/lang/ClassLoader;)V"); - v[0].l = cl; - m_BootLoader = JPObjectRef(frame, frame.NewObjectA(dyClass, newDyLoader, v)); - + // org.jpype was not loaded already so we can't proceed + JP_RAISE(PyExc_RuntimeError, "Can't find org.jpype.jar support library"); JP_TRACE_OUT; // GCOVR_EXCL_LINE } diff --git a/native/java/manifest.txt b/native/java/manifest.txt new file mode 100644 index 000000000..85c360946 --- /dev/null +++ b/native/java/manifest.txt @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Premain-Class: org.jpype.agent.JPypeAgent diff --git a/native/java/org/jpype/agent/JPypeAgent.java b/native/java/org/jpype/agent/JPypeAgent.java new file mode 100644 index 000000000..9d02e4ba2 --- /dev/null +++ b/native/java/org/jpype/agent/JPypeAgent.java @@ -0,0 +1,10 @@ +package org.jpype.agent; + +import java.lang.instrument.Instrumentation; + +public class JPypeAgent +{ + public static void premain(String agentArgs, Instrumentation inst) { + System.out.println("Start jpype"); + } +} diff --git a/project/jpype_java/manifest.mf b/project/jpype_java/manifest.mf new file mode 100644 index 000000000..85c360946 --- /dev/null +++ b/project/jpype_java/manifest.mf @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Premain-Class: org.jpype.agent.JPypeAgent diff --git a/project/jpype_java/nbproject/project.properties b/project/jpype_java/nbproject/project.properties index 76c7151ac..da12f5cef 100755 --- a/project/jpype_java/nbproject/project.properties +++ b/project/jpype_java/nbproject/project.properties @@ -113,3 +113,4 @@ source.encoding=UTF-8 src.java.dir=${file.reference.native-java} test.harness.dir=${file.reference.test-harness} test.src.dir=test +manifest.file=manifest.mf diff --git a/setupext/build_ext.py b/setupext/build_ext.py index 80514bb93..00cc0a72d 100644 --- a/setupext/build_ext.py +++ b/setupext/build_ext.py @@ -315,8 +315,12 @@ def build_java_ext(self, ext): os.makedirs("build/classes", exist_ok=True) self.announce(" %s" % " ".join(cmd1), level=distutils.log.INFO) subprocess.check_call(cmd1) + manifest = None try: for file in glob.iglob("native/java/**/*.*", recursive=True): + if file.endswith("manifest.txt"): + manifest = file + continue if file.endswith(".java") or os.path.isdir(file): continue p = os.path.join(build_dir, os.path.relpath(file, "native/java")) @@ -326,7 +330,7 @@ def build_java_ext(self, ext): print("FAIL", ex) pass cmd3 = shlex.split( - '%s cvf "%s" -C "%s" .' % (jar, jarFile, build_dir)) + '%s cvfm "%s" "%s" -C "%s" .' % (jar, jarFile, manifest, build_dir)) self.announce(" %s" % " ".join(cmd3), level=distutils.log.INFO) subprocess.check_call(cmd3) From b45f56c326b252453e74eba434faf804c81d84d9 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 23 Jun 2024 13:57:18 -0700 Subject: [PATCH 03/11] Cleanup --- native/common/jp_classloader.cpp | 1 - native/java/org/jpype/agent/JPypeAgent.java | 3 ++- project/jpype_java/manifest.mf | 2 -- project/jpype_java/nbproject/project.properties | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 project/jpype_java/manifest.mf diff --git a/native/common/jp_classloader.cpp b/native/common/jp_classloader.cpp index 78023283b..0dbfa750d 100644 --- a/native/common/jp_classloader.cpp +++ b/native/common/jp_classloader.cpp @@ -17,7 +17,6 @@ #include #include #include -#include jobject JPClassLoader::getBootLoader() { diff --git a/native/java/org/jpype/agent/JPypeAgent.java b/native/java/org/jpype/agent/JPypeAgent.java index 9d02e4ba2..c15ac7334 100644 --- a/native/java/org/jpype/agent/JPypeAgent.java +++ b/native/java/org/jpype/agent/JPypeAgent.java @@ -5,6 +5,7 @@ public class JPypeAgent { public static void premain(String agentArgs, Instrumentation inst) { - System.out.println("Start jpype"); + // This doesn't have to do anything. + // We just need to be an agent to load elevated privileges } } diff --git a/project/jpype_java/manifest.mf b/project/jpype_java/manifest.mf deleted file mode 100644 index 85c360946..000000000 --- a/project/jpype_java/manifest.mf +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 -Premain-Class: org.jpype.agent.JPypeAgent diff --git a/project/jpype_java/nbproject/project.properties b/project/jpype_java/nbproject/project.properties index da12f5cef..45172cca8 100755 --- a/project/jpype_java/nbproject/project.properties +++ b/project/jpype_java/nbproject/project.properties @@ -113,4 +113,4 @@ source.encoding=UTF-8 src.java.dir=${file.reference.native-java} test.harness.dir=${file.reference.test-harness} test.src.dir=test -manifest.file=manifest.mf +manifest.file=../../native/jara/manifest.txt From 9a2a604c8ba39d05ab9eb80a4c8327862727f117 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Fri, 18 Oct 2024 17:31:19 -0700 Subject: [PATCH 04/11] Restore the missing arguments. Next determine the conflict --- jpype/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpype/_core.py b/jpype/_core.py index 8ea045787..9c7945ed7 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -253,7 +253,7 @@ def startJVM( # Keep the current locale settings, else Java will replace them. prior = [locale.getlocale(i) for i in categories] # Start the JVM - _jpype.startup(jvmpath, jvmargs, + _jpype.startup(jvmpath, jvmargs + extra_jvm_args, ignoreUnrecognized, convertStrings, interrupt) # Collect required resources for operation initializeResources() From 9d66c27d059381018f413bd30a7f2933c44a5592 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Fri, 18 Oct 2024 17:58:27 -0700 Subject: [PATCH 05/11] Backing out #1195 and working on replacement --- jpype/_core.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index 9c7945ed7..5ea7850bf 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -151,7 +151,7 @@ def _handleClassPath( out.extend(glob.glob(pth + '.jar')) else: out.append(pth) - return out + return _classpath._SEP.join(out) _JVM_started = False @@ -238,6 +238,7 @@ def startJVM( classpath = _classpath.getClassPath() # Handle strings and list of strings. + extra_jvm_args = () if classpath: extra_jvm_args += (f'-Djava.class.path={_handleClassPath(classpath)}', ) @@ -273,20 +274,20 @@ def startJVM( raise RuntimeError(f"{jvmpath} is older than required Java version{version}") from ex raise - """Prior versions of JPype used the jvmargs to setup the class paths via - JNI (Java Native Interface) option strings: - i.e -Djava.class.path=... - See: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html - - Unfortunately, unicode is unsupported by this interface on windows, since - windows uses wide-byte (16bit) character encoding. - See: https://stackoverflow.com/questions/20052455/jni-start-jvm-with-unicode-support - - To resolve this issue we add the classpath after initialization since jpype - itself supports unicode class paths. - """ - for cp in _handleClassPath(classpath): - addClassPath(Path.cwd() / Path(cp).resolve()) +# """Prior versions of JPype used the jvmargs to setup the class paths via +# JNI (Java Native Interface) option strings: +# i.e -Djava.class.path=... +# See: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html +# +# Unfortunately, unicode is unsupported by this interface on windows, since +# windows uses wide-byte (16bit) character encoding. +# See: https://stackoverflow.com/questions/20052455/jni-start-jvm-with-unicode-support +# +# To resolve this issue we add the classpath after initialization since jpype +# itself supports unicode class paths. +# """ +# for cp in _handleClassPath(classpath): +# addClassPath(Path.cwd() / Path(cp).resolve()) def initializeResources(): From 8bfb5b0fb56bfb6f4533c89969508b453f5aacd5 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Fri, 18 Oct 2024 19:21:36 -0700 Subject: [PATCH 06/11] Fix PR collision --- jpype/_core.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index 5ea7850bf..886f6198b 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -120,6 +120,7 @@ def _hasClassPath(args) -> bool: def _handleClassPath( classpath: typing.Union[typing.Sequence[_PathOrStr], _PathOrStr, None] = None, + ascii: bool = True ) -> typing.Sequence[str]: """ Return a classpath which represents the given tuple of classpath specifications @@ -151,7 +152,9 @@ def _handleClassPath( out.extend(glob.glob(pth + '.jar')) else: out.append(pth) - return _classpath._SEP.join(out) + if ascii: + return _classpath._SEP.join([i for i in out if i.isascii()]) + return [i for i in out if not i.isascii()] _JVM_started = False @@ -274,20 +277,20 @@ def startJVM( raise RuntimeError(f"{jvmpath} is older than required Java version{version}") from ex raise -# """Prior versions of JPype used the jvmargs to setup the class paths via -# JNI (Java Native Interface) option strings: -# i.e -Djava.class.path=... -# See: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html -# -# Unfortunately, unicode is unsupported by this interface on windows, since -# windows uses wide-byte (16bit) character encoding. -# See: https://stackoverflow.com/questions/20052455/jni-start-jvm-with-unicode-support -# -# To resolve this issue we add the classpath after initialization since jpype -# itself supports unicode class paths. -# """ -# for cp in _handleClassPath(classpath): -# addClassPath(Path.cwd() / Path(cp).resolve()) + """Prior versions of JPype used the jvmargs to setup the class paths via + JNI (Java Native Interface) option strings: + i.e -Djava.class.path=... + See: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html + + Unfortunately, unicode is unsupported by this interface on windows, since + windows uses wide-byte (16bit) character encoding. + See: https://stackoverflow.com/questions/20052455/jni-start-jvm-with-unicode-support + + To resolve this issue we add the classpath after initialization since jpype + itself supports unicode class paths. + """ + for cp in _handleClassPath(classpath, False): + addClassPath(Path.cwd() / Path(cp).resolve()) def initializeResources(): From 49c32bcad806133a7214b2888031cde964556caa Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 19 Oct 2024 10:58:37 -0700 Subject: [PATCH 07/11] Another attempt --- jpype/_core.py | 5 +++-- native/java/manifest.txt | 2 +- native/java/org/jpype/proxy/JPypeProxy.java | 6 +++++- project/jpype_java/nbproject/project.properties | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index 886f6198b..0512a1a2d 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -153,7 +153,7 @@ def _handleClassPath( else: out.append(pth) if ascii: - return _classpath._SEP.join([i for i in out if i.isascii()]) + return [i for i in out if i.isascii()] return [i for i in out if not i.isascii()] @@ -243,7 +243,8 @@ def startJVM( # Handle strings and list of strings. extra_jvm_args = () if classpath: - extra_jvm_args += (f'-Djava.class.path={_handleClassPath(classpath)}', ) + cp = _classpath._SEP.join(_handleClassPath(classpath)) + extra_jvm_args += ('-Djava.class.path=%s'%cp, ) supportLib = os.path.join(os.path.dirname(os.path.dirname(__file__)), "org.jpype.jar") if not os.path.exists(supportLib): diff --git a/native/java/manifest.txt b/native/java/manifest.txt index 85c360946..7a0699326 100644 --- a/native/java/manifest.txt +++ b/native/java/manifest.txt @@ -1,2 +1,2 @@ Manifest-Version: 1.0 -Premain-Class: org.jpype.agent.JPypeAgent +Premain-Class: org.jpype.agent.JPypeAgent \ No newline at end of file diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/org/jpype/proxy/JPypeProxy.java index aae264ec4..cf35ab4cc 100644 --- a/native/java/org/jpype/proxy/JPypeProxy.java +++ b/native/java/org/jpype/proxy/JPypeProxy.java @@ -16,6 +16,7 @@ package org.jpype.proxy; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -102,7 +103,10 @@ public Object invoke(Object proxy, Method method, Object[] args) if (method.isDefault()) { return MethodHandles.lookup() - .unreflectSpecial(method, method.getDeclaringClass()) + .findSpecial(method.getDeclaringClass(), + method.getName(), + MethodType.methodType(method.getReturnType()), + method.getDeclaringClass()) .bindTo(proxy) .invokeWithArguments(args); } diff --git a/project/jpype_java/nbproject/project.properties b/project/jpype_java/nbproject/project.properties index 45172cca8..3ec6b403a 100755 --- a/project/jpype_java/nbproject/project.properties +++ b/project/jpype_java/nbproject/project.properties @@ -113,4 +113,4 @@ source.encoding=UTF-8 src.java.dir=${file.reference.native-java} test.harness.dir=${file.reference.test-harness} test.src.dir=test -manifest.file=../../native/jara/manifest.txt +manifest.file=../../native/java/manifest.txt From 0fd5338ebd021119f2b5e98b038bc7f618ee0847 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 19 Oct 2024 11:04:01 -0700 Subject: [PATCH 08/11] Why is this failing??? --- native/java/manifest.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/native/java/manifest.txt b/native/java/manifest.txt index 7a0699326..0b556c29d 100644 --- a/native/java/manifest.txt +++ b/native/java/manifest.txt @@ -1,2 +1,3 @@ Manifest-Version: 1.0 -Premain-Class: org.jpype.agent.JPypeAgent \ No newline at end of file +Premain-Class: org.jpype.agent.JPypeAgent + From 9bd2cd06d4c6fe7ac0daade1e6d8f0ce34c3b54b Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 19 Oct 2024 12:11:47 -0700 Subject: [PATCH 09/11] Pattern for Java 8 --- native/java/org/jpype/proxy/JPypeProxy.java | 66 +++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/org/jpype/proxy/JPypeProxy.java index cf35ab4cc..c99c93244 100644 --- a/native/java/org/jpype/proxy/JPypeProxy.java +++ b/native/java/org/jpype/proxy/JPypeProxy.java @@ -16,10 +16,14 @@ package org.jpype.proxy; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jpype.JPypeContext; import org.jpype.manager.TypeManager; import org.jpype.ref.JPypeReferenceQueue; @@ -31,6 +35,7 @@ public class JPypeProxy implements InvocationHandler { + private final static Constructor constructor; private final static JPypeReferenceQueue referenceQueue = JPypeReferenceQueue.getInstance(); JPypeContext context; public long instance; @@ -39,6 +44,26 @@ public class JPypeProxy implements InvocationHandler ClassLoader cl = ClassLoader.getSystemClassLoader(); public static Object missing = new Object(); + // See following link for Java 8 default access implementation + // https://blog.jooq.org/correct-reflective-access-to-interface-default-methods-in-java-8-9-10/ + static + { + Constructor c = null; + if (System.getProperty("java.version").startsWith("1.")) + { + try + { + c = Lookup.class + .getDeclaredConstructor(Class.class); + c.setAccessible(true); + } catch (NoSuchMethodException | SecurityException ex) + { + Logger.getLogger(JPypeProxy.class.getName()).log(Level.SEVERE, null, ex); + } + } + constructor = c; + } + public static JPypeProxy newProxy(JPypeContext context, long instance, long cleanup, @@ -94,23 +119,44 @@ public Object invoke(Object proxy, Method method, Object[] args) // Check first to see if Python has implementated it Object result = hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args, missing); - + // If we get a good result than return it if (result != missing) return result; - + // If it is a default method in the interface then we have to invoke it using special reflection. if (method.isDefault()) { - return MethodHandles.lookup() - .findSpecial(method.getDeclaringClass(), - method.getName(), - MethodType.methodType(method.getReturnType()), - method.getDeclaringClass()) - .bindTo(proxy) - .invokeWithArguments(args); + try + { + Class cls = method.getDeclaringClass(); + + // Java 8 + if (constructor != null) + { + return constructor.newInstance(cls) + .findSpecial(cls, + method.getName(), + MethodType.methodType(method.getReturnType()), + cls) + .bindTo(proxy) + .invokeWithArguments(args); + } + + return MethodHandles.lookup() + .in(cls) + .findSpecial(cls, + method.getName(), + MethodType.methodType(method.getReturnType()), + cls) + .bindTo(proxy) + .invokeWithArguments(args); + } catch (java.lang.IllegalAccessException ex) + { + throw new RuntimeException(ex); + } } - + // Else throw... (this should never happen as proxies are checked when created.) throw new NoSuchMethodError(method.getName()); } From 51aee96b2210241f12a0927b038e6d03ac67a9df Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 19 Oct 2024 12:19:04 -0700 Subject: [PATCH 10/11] Go away Mypy --- jpype/_core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jpype/_core.py b/jpype/_core.py index 0512a1a2d..e4f547247 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -241,15 +241,15 @@ def startJVM( classpath = _classpath.getClassPath() # Handle strings and list of strings. - extra_jvm_args = () + extra_jvm_args = [] if classpath: cp = _classpath._SEP.join(_handleClassPath(classpath)) - extra_jvm_args += ('-Djava.class.path=%s'%cp, ) + extra_jvm_args += ['-Djava.class.path=%s'%cp ] supportLib = os.path.join(os.path.dirname(os.path.dirname(__file__)), "org.jpype.jar") if not os.path.exists(supportLib): raise RuntimeError("Unable to find org.jpype.jar support library at " + supportLib) - extra_jvm_args += ('-javaagent:' + supportLib,) + extra_jvm_args += ['-javaagent:' + supportLib] try: import locale @@ -258,7 +258,7 @@ def startJVM( # Keep the current locale settings, else Java will replace them. prior = [locale.getlocale(i) for i in categories] # Start the JVM - _jpype.startup(jvmpath, jvmargs + extra_jvm_args, + _jpype.startup(jvmpath, jvmargs + tuple(extra_jvm_args), ignoreUnrecognized, convertStrings, interrupt) # Collect required resources for operation initializeResources() From b5cdd55838d1f2688c7c48c5a6504566d5686630 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 19 Oct 2024 12:24:49 -0700 Subject: [PATCH 11/11] Try yet again --- native/java/org/jpype/proxy/JPypeProxy.java | 1 - 1 file changed, 1 deletion(-) diff --git a/native/java/org/jpype/proxy/JPypeProxy.java b/native/java/org/jpype/proxy/JPypeProxy.java index c99c93244..836deb74a 100644 --- a/native/java/org/jpype/proxy/JPypeProxy.java +++ b/native/java/org/jpype/proxy/JPypeProxy.java @@ -144,7 +144,6 @@ public Object invoke(Object proxy, Method method, Object[] args) } return MethodHandles.lookup() - .in(cls) .findSpecial(cls, method.getName(), MethodType.methodType(method.getReturnType()),