Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d6dbda2

Browse files
committedApr 20, 2024
Fixes #1181
1 parent 904fc43 commit d6dbda2

File tree

10 files changed

+75
-46
lines changed

10 files changed

+75
-46
lines changed
 

‎doc/CHANGELOG.rst

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This changelog *only* contains changes from the *first* pypi release (0.5.4.3) o
66
Latest Changes:
77

88
- **1.5.1_dev0 - 2023-12-15**
9+
10+
- Allow access to default methods implemented in interfaces when using ``@JImplements``.
11+
912
- Use PEP-518 and PEP-660 configuration for the package, allowing editable and
1013
configurable builds using modern Python packaging tooling.
1114
Where before ``python setup.py --enable-tracing develop``, now can be done with

‎native/common/include/jp_context.h

-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ class JPContext
220220
public:
221221
JPClassRef m_ContextClass;
222222
JPClassRef m_RuntimeException;
223-
JPClassRef m_NoSuchMethodError;
224223

225224
private:
226225
JPClassRef m_Array;

‎native/common/include/jp_exception.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ _python_error,
5858
_python_exc,
5959
_os_error_unix,
6060
_os_error_windows,
61-
_method_not_found,
6261
};
6362

6463
// Create a stackinfo for a particular location in the code that can then
@@ -160,4 +159,4 @@ class JPypeException : std::runtime_error
160159
JPThrowableRef m_Throwable;
161160
};
162161

163-
#endif
162+
#endif

‎native/common/include/jpype.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ class JPResource
168168
#define JP_RAISE_PYTHON() { throw JPypeException(JPError::_python_error, nullptr, JP_STACKINFO()); }
169169
#define JP_RAISE_OS_ERROR_UNIX(err, msg) { throw JPypeException(JPError::_os_error_unix, msg, err, JP_STACKINFO()); }
170170
#define JP_RAISE_OS_ERROR_WINDOWS(err, msg) { throw JPypeException(JPError::_os_error_windows, msg, err, JP_STACKINFO()); }
171-
#define JP_RAISE_METHOD_NOT_FOUND(msg) { throw JPypeException(JPError::_method_not_found, nullptr, msg, JP_STACKINFO()); }
172171
#define JP_RAISE(type, msg) { throw JPypeException(JPError::_python_exc, type, msg, JP_STACKINFO()); }
173172

174173
#ifndef PyObject_HEAD
@@ -196,4 +195,4 @@ using PyObject = _object;
196195
// Primitives classes
197196
#include "jp_primitivetype.h"
198197

199-
#endif // _JPYPE_H_
198+
#endif // _JPYPE_H_

‎native/common/jp_context.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ void JPContext::initializeResources(JNIEnv* env, bool interrupt)
181181
m_Object_HashCodeID = frame.GetMethodID(objectClass, "hashCode", "()I");
182182
m_Object_GetClassID = frame.GetMethodID(objectClass, "getClass", "()Ljava/lang/Class;");
183183

184-
m_NoSuchMethodError = JPClassRef(frame, (jclass) frame.FindClass("java/lang/NoSuchMethodError"));
185184
m_RuntimeException = JPClassRef(frame, (jclass) frame.FindClass("java/lang/RuntimeException"));
186185

187186
jclass stringClass = frame.FindClass("java/lang/String");

‎native/common/jp_exception.cpp

-12
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,6 @@ void JPypeException::toPython()
303303
} else if (m_Type == JPError::_python_error)
304304
{
305305
// Already on the stack
306-
} else if (m_Type == JPError::_method_not_found)
307-
{
308-
// This is hit when a proxy fails to implement a required
309-
// method. Only older style proxies should be able hit this.
310-
JP_TRACE("Runtime error");
311-
PyErr_SetString(PyExc_RuntimeError, mesg);
312306
}// This section is only reachable during startup of the JVM.
313307
// GCOVR_EXCL_START
314308
else if (m_Type == JPError::_os_error_unix)
@@ -428,12 +422,6 @@ void JPypeException::toJava(JPContext *context)
428422
return;
429423
}
430424

431-
if (m_Type == JPError::_method_not_found)
432-
{
433-
frame.ThrowNew(context->m_NoSuchMethodError.get(), mesg);
434-
return;
435-
}
436-
437425
if (m_Type == JPError::_python_error)
438426
{
439427
JPPyCallAcquire callback;

‎native/common/jp_proxy.cpp

+3-6
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke(
5252
jlong hostObj,
5353
jlong returnTypePtr,
5454
jlongArray parameterTypePtrs,
55-
jobjectArray args)
55+
jobjectArray args,
56+
jobject missing)
5657
{
5758
auto* context = (JPContext*) contextPtr;
5859
JPJavaFrame frame = JPJavaFrame::external(context, env);
@@ -84,11 +85,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_jpype_proxy_JPypeProxy_hostInvoke(
8485

8586
// If method can't be called, throw an exception
8687
if (callable.isNull() || callable.get() == Py_None)
87-
{
88-
JP_TRACE("Callable not found");
89-
JP_RAISE_METHOD_NOT_FOUND(cname);
90-
return nullptr;
91-
}
88+
return missing;
9289

9390
// Find the return type
9491
auto* returnClass = (JPClass*) returnTypePtr;

‎native/java/org/jpype/proxy/JPypeProxy.java

+35-22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
**************************************************************************** */
1616
package org.jpype.proxy;
1717

18+
import java.lang.invoke.MethodHandles;
1819
import java.lang.reflect.InvocationHandler;
1920
import java.lang.reflect.Method;
2021
import java.lang.reflect.Proxy;
@@ -35,6 +36,7 @@ public class JPypeProxy implements InvocationHandler
3536
public long cleanup;
3637
Class<?>[] interfaces;
3738
ClassLoader cl = ClassLoader.getSystemClassLoader();
39+
public static Object missing = new Object();
3840

3941
public static JPypeProxy newProxy(JPypeContext context,
4042
long instance,
@@ -69,35 +71,46 @@ public Object newInstance()
6971
public Object invoke(Object proxy, Method method, Object[] args)
7072
throws Throwable
7173
{
72-
try
73-
{
74-
// context.incrementProxy();
75-
if (context.isShutdown())
76-
throw new RuntimeException("Proxy called during shutdown");
7774

78-
// We can save a lot of effort on the C++ side by doing all the
79-
// type lookup work here.
80-
TypeManager typeManager = context.getTypeManager();
81-
long returnType;
82-
long[] parameterTypes;
83-
synchronized (typeManager)
75+
if (context.isShutdown())
76+
throw new RuntimeException("Proxy called during shutdown");
77+
78+
// We can save a lot of effort on the C++ side by doing all the
79+
// type lookup work here.
80+
TypeManager typeManager = context.getTypeManager();
81+
long returnType;
82+
long[] parameterTypes;
83+
synchronized (typeManager)
84+
{
85+
returnType = typeManager.findClass(method.getReturnType());
86+
Class<?>[] types = method.getParameterTypes();
87+
parameterTypes = new long[types.length];
88+
for (int i = 0; i < types.length; ++i)
8489
{
85-
returnType = typeManager.findClass(method.getReturnType());
86-
Class<?>[] types = method.getParameterTypes();
87-
parameterTypes = new long[types.length];
88-
for (int i = 0; i < types.length; ++i)
89-
{
90-
parameterTypes[i] = typeManager.findClass(types[i]);
91-
}
90+
parameterTypes[i] = typeManager.findClass(types[i]);
9291
}
92+
}
9393

94-
return hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args);
95-
} finally
94+
// Check first to see if Python has implementated it
95+
Object result = hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args, missing);
96+
97+
// If we get a good result than return it
98+
if (result != missing)
99+
return result;
100+
101+
// If it is a default method in the interface then we have to invoke it using special reflection.
102+
if (method.isDefault())
96103
{
97-
// context.decrementProxy();
104+
return MethodHandles.lookup()
105+
.unreflectSpecial(method, method.getDeclaringClass())
106+
.bindTo(proxy)
107+
.invokeWithArguments(args);
98108
}
109+
110+
// Else throw... (this should never happen as proxies are checked when created.)
111+
throw new NoSuchMethodError(method.getName());
99112
}
100113

101114
private static native Object hostInvoke(long context, String name, long pyObject,
102-
long returnType, long[] argsTypes, Object[] args);
115+
long returnType, long[] argsTypes, Object[] args, Object bad);
103116
}

‎test/harness/jpype/proxy/TestInterface1.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ public interface TestInterface1
1919
{
2020

2121
int testMethod1();
22+
23+
default int testDefault() { return 1234; }
2224
}

‎test/jpypetest/test_proxy.py

+30
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ class MyImpl(object):
135135
def testMethod1(self):
136136
pass
137137

138+
def testDefault1(self):
139+
itf1 = self.package.TestInterface1
140+
141+
@JImplements(itf1)
142+
class MyImpl(object):
143+
@JOverride
144+
def testMethod1(self):
145+
pass
146+
147+
obj = itf1@MyImpl()
148+
self.assertEqual(obj.testDefault(), 1234)
149+
150+
def testDefault2(self):
151+
itf1 = self.package.TestInterface1
152+
153+
@JImplements(itf1)
154+
class MyImpl(object):
155+
@JOverride
156+
def testMethod1(self):
157+
pass
158+
159+
@JOverride
160+
def testDefault(self):
161+
return 5678
162+
163+
obj = itf1@MyImpl()
164+
self.assertEqual(obj.testDefault(), 5678)
165+
138166
def testProxyImplementsForm2(self):
139167
itf1 = self.package.TestInterface1
140168
itf2 = self.package.TestInterface2
@@ -560,3 +588,5 @@ def run(self):
560588

561589
startJVM()
562590
assert isinstance(MyImpl(), MyImpl)
591+
592+

0 commit comments

Comments
 (0)
Please sign in to comment.