Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Thrameos committed Apr 20, 2024
1 parent 904fc43 commit d6dbda2
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 46 deletions.
3 changes: 3 additions & 0 deletions doc/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion native/common/include/jp_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ class JPContext
public:
JPClassRef m_ContextClass;
JPClassRef m_RuntimeException;
JPClassRef m_NoSuchMethodError;

private:
JPClassRef m_Array;
Expand Down
3 changes: 1 addition & 2 deletions native/common/include/jp_exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -160,4 +159,4 @@ class JPypeException : std::runtime_error
JPThrowableRef m_Throwable;
};

#endif
#endif
3 changes: 1 addition & 2 deletions native/common/include/jpype.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -196,4 +195,4 @@ using PyObject = _object;
// Primitives classes
#include "jp_primitivetype.h"

#endif // _JPYPE_H_
#endif // _JPYPE_H_
1 change: 0 additions & 1 deletion native/common/jp_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
12 changes: 0 additions & 12 deletions native/common/jp_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 3 additions & 6 deletions native/common/jp_proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
57 changes: 35 additions & 22 deletions native/java/org/jpype/proxy/JPypeProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions test/harness/jpype/proxy/TestInterface1.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface TestInterface1
{

int testMethod1();

default int testDefault() { return 1234; }
}
30 changes: 30 additions & 0 deletions test/jpypetest/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -560,3 +588,5 @@ def run(self):

startJVM()
assert isinstance(MyImpl(), MyImpl)


0 comments on commit d6dbda2

Please sign in to comment.