Skip to content

Commit

Permalink
Merge pull request #1182 from Thrameos/proxy_default
Browse files Browse the repository at this point in the history
Access to default methods in ``@JImplements``
  • Loading branch information
Thrameos authored Oct 24, 2024
2 parents dc124d6 + b5cdd55 commit 002c616
Show file tree
Hide file tree
Showing 16 changed files with 161 additions and 104 deletions.
2 changes: 2 additions & 0 deletions doc/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Latest Changes:

- **1.5.1_dev0 - 2023-12-15**

- Allow access to default methods implemented in interfaces when using ``@JImplements``.

- Added support for typing ``JArray`` (Java type only), e.g. ``JArray[java.lang.Object]`` ``"JArray[java.lang.Object]"``

- Fixed uncaught exception while setting traceback causing issues in Python 3.11/3.12.
Expand Down
20 changes: 17 additions & 3 deletions jpype/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -151,7 +152,9 @@ def _handleClassPath(
out.extend(glob.glob(pth + '.jar'))
else:
out.append(pth)
return out
if ascii:
return [i for i in out if i.isascii()]
return [i for i in out if not i.isascii()]


_JVM_started = False
Expand Down Expand Up @@ -237,14 +240,25 @@ def startJVM(
# Not specified at all, use the default classpath.
classpath = _classpath.getClassPath()

# Handle strings and list of strings.
extra_jvm_args = []
if 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):
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)
categories = [getattr(locale, i) for i in dir(locale) if i.startswith('LC_') and i != 'LC_ALL']
# 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 + tuple(extra_jvm_args),
ignoreUnrecognized, convertStrings, interrupt)
# Collect required resources for operation
initializeResources()
Expand Down Expand Up @@ -276,7 +290,7 @@ def startJVM(
To resolve this issue we add the classpath after initialization since jpype
itself supports unicode class paths.
"""
for cp in _handleClassPath(classpath):
for cp in _handleClassPath(classpath, False):
addClassPath(Path.cwd() / Path(cp).resolve())


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
1 change: 0 additions & 1 deletion 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
57 changes: 2 additions & 55 deletions native/common/jp_classloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,6 @@ 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, "<init>", "(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");
Expand Down Expand Up @@ -68,44 +51,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, "<init>", "([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, "<init>", "(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
}

Expand Down
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 @@ -304,12 +304,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 @@ -429,12 +423,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
3 changes: 3 additions & 0 deletions native/java/manifest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Premain-Class: org.jpype.agent.JPypeAgent

11 changes: 11 additions & 0 deletions native/java/org/jpype/agent/JPypeAgent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.jpype.agent;

import java.lang.instrument.Instrumentation;

public class JPypeAgent
{
public static void premain(String agentArgs, Instrumentation inst) {
// This doesn't have to do anything.
// We just need to be an agent to load elevated privileges
}
}
106 changes: 84 additions & 22 deletions native/java/org/jpype/proxy/JPypeProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
**************************************************************************** */
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;
Expand All @@ -29,12 +35,34 @@
public class JPypeProxy implements InvocationHandler
{

private final static Constructor<Lookup> constructor;
private final static JPypeReferenceQueue referenceQueue = JPypeReferenceQueue.getInstance();
JPypeContext context;
public long instance;
public long cleanup;
Class<?>[] interfaces;
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<Lookup> 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,
Expand Down Expand Up @@ -69,35 +97,69 @@ public Object newInstance()
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
try

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)
{
// 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)
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]);
}
}

// 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;

return hostInvoke(context.getContext(), method.getName(), instance, returnType, parameterTypes, args);
} finally
// If it is a default method in the interface then we have to invoke it using special reflection.
if (method.isDefault())
{
// context.decrementProxy();
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()
.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());
}

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);
}
1 change: 1 addition & 0 deletions project/jpype_java/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=../../native/java/manifest.txt
Loading

0 comments on commit 002c616

Please sign in to comment.