diff --git a/.gitignore b/.gitignore index 8d24aecdb9..b095b6207d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ Vagrantfile *.so *.dll +/bin/ diff --git a/CHANGES.md b/CHANGES.md index 196efc385d..e004ef7249 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,21 @@ jpy Changelog ************* +Version 0.10 (in development) +============================= + +* Make jpy work with Anaconda by setting environment variable + `PYTHONHOME` from Java + [#143](https://github.com/bcdev/jpy/issues/143). Contribution by Dr-Irv. +* Fixed: Constants are not properly passed from Java to Python when using interfaces + [#140](https://github.com/bcdev/jpy/issues/140). Contribution by Dr-Irv. +* Fixed: Cannot iterate through a dict in Python 3.x + [#136](https://github.com/bcdev/jpy/issues/136). Contribution by Dr-Irv. + Version 0.9 =========== -This version includes a number of contributions from supportive GitHub users. Thanks to all of you! +This version includes a number of contributions from supportive GitHub users. Thanks to all of you! Fixes ----- @@ -14,18 +25,31 @@ Fixes * Fixed problem where default methods on Java 8 Interfaces were not found (issue #102). Fix by Charles P. Wright. * Fixed error caused by missing `sys.argv` in Python when called from Java (issue #81). Fix by Dave Voutila. * Fixed problem where calling jpy.get_type() too many times causes a memory access error (issue #74). Fix by Dave Voutila. -* Fixed a corruption when retrieving long values (#72). Fix by chipkent. +* Fixed a corruption when retrieving long values (#72). Fix by chipkent. * Fixed fatal error when stopping python session (issue #70, #77). Fix by Dave Voutila. +# Explicit null checks for avoiding JVM crash (issue #126). Fix by Geomatys. Improvements ------------ * Can now use pip to install Python `jpy` package directly from GitHub (#83). - This works for Linux and OS X where C compilers are available by default - and should work on Windows with Visual Studio 15 installed. - Contribution by Dave Voutila. -* Java `PyObject` is now serializable. Contribution by Mario Briggs. - + This works for Linux and OS X where C compilers are available by default + and should work on Windows with Visual Studio 15 installed. + Contribution by Dave Voutila. +* Java `PyObject` is now serializable. Contribution by Mario Briggs. +* Improved Varargs method matching. You may pass in either an array (as in the + past) or individual Python arguments, the match for a varargs method call is + the minimum match for each of the arguments. Zero length arrays (i.e. no + arguments) are also permitted with a match value of 10. +* `jpy.type_translations` dictionary for callbacks when instantiating Python objects. +* `jpy.VerboseExceptions` enables full Java stack traces. +* More Python exceptions are translated to the corresponding Java type. +* Globals and locals are converted when executing code with PyLib, to allow variables to be + used across statement invocation; and interrogated from Java. +* PyObject wrappers for dictionary, list, and introspection functions to tell + you whether or not you can convert the object. +* Support for isAssignable checks when dealing with Python Strings and primitives, to allow + matches for argument types such as `java.lang.Comparable` or `java.lang.Number`. Version 0.8 =========== diff --git a/README.md b/README.md index 4ac8ce4d4b..e7297f6272 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/bcdev/jpy.svg?branch=master)](https://travis-ci.org/bcdev/jpy) -[![Build Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/forman/jpy) +[![Build Status](https://ci.appveyor.com/api/projects/status/ywkcey4nlt0avasf?svg=true)](https://ci.appveyor.com/project/bcdev/jpy) [![Documentation Status](https://readthedocs.org/projects/jpy/badge/?version=latest)](http://jpy.readthedocs.org/en/latest/?badge=latest) diff --git a/appveyor.yml b/appveyor.yml index 0491b3a7e8..a8c0b56cbf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,3 +66,18 @@ install: build_script: - set PATH=%JPY_PYTHON_HOME%;%JPY_PYTHON_HOME%\DLLs;%JPY_JDK_HOME%;%JPY_JDK_HOME%\bin;%PATH% - call "%JPY_PYTHON_HOME%\python.exe" setup.py --maven bdist_wheel + - move dist win + +artifacts: + - path: 'win\*.whl' + name: wheel + +deploy: + - provider: FTP + protocol: ftp + host: ftp.brockmann-consult.de + username: jpy + password: + secure: AMte8IErI/LRGmLGq4Y5YQ== + folder: software + debug: true \ No newline at end of file diff --git a/doc/install.rst b/doc/install.rst index e89f476195..44642ad2f5 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -84,12 +84,23 @@ Another optional parameter * ``jpy.debug`` - which is either ``true`` or ``false`` can be used to output extra debugging information. +The optional parameter + +* ``jpy.pythonPrefix`` - points to the location of the PYTHONHOME. If set, then jpy will call ``PyLib.setPythonHome()`` with the value of this parameter. + All the parameters can be passed directly to the JVM either as Java system properties or by using the single system property * ``jpy.config`` - which is a path to a Java properties files containing the definitions of the two parameters named above. Such property file is also written for each build and is found in ``build/lib--/jpyconfig.properties``. +Setting PYTHONHOME +------------------ + +If the environment variable ``PYTHONHOME`` is not set when you call Python from Java, you may get an error about +file system encodings not being found. It is possible to set the location of Python from your +Java program. Use ``PyLib.setPythonHome(pathToPythonHome)`` to do that, where ``pathToPythonHome`` is a ``String`` that +contains the location of the Python installation, or as described above, use the parameter ``jpy.pytyhonPrefix``. ======================== Build for Linux / Darwin diff --git a/doc/intro.rst b/doc/intro.rst index 3380cd5f6c..bce161551c 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -90,11 +90,8 @@ then we can call the Python code from Java as follows :: // Import the Python module PyModule plugInModule = PyLib.importModule("bibo_plugin"); - // Get the Python class - PyObject plugInClass = plugInModule.getAttribute("BiboPlugIn"); - // Call the Python class to instantiate an object - PyObject plugInObj = plugInClass.call(); + PyObject plugInObj = plugInModule.call("BiboPlugin"); // Create a Java proxy object for the Python object PlugIn plugIn = plugInObj.createProxy(PlugIn.class); diff --git a/doc/reference.rst b/doc/reference.rst index bb6fd55f3b..c6d2fb6c8b 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -218,6 +218,20 @@ Variables Here a call to the ``read`` method will modify the numpy array's content as desired and return the same array instance as indicated by the Java method's specification. +.. py:data:: type_translations + :module: jpy + + Contains callbacks which are called when instantiating a Python object from a Java object. + After the standard wrapping of the Java object as a Python object, the Java type name is looked up in this + dictionary. If the returned item is a callable, the callable is called with the JPy object as an argument, + and the callable's result is returned to the user. + + +.. py:data:: VerboseExceptions.enabled + :module: jpy + + If set to true, then jpy will produce more verbose exception messages; which include the full Java stack trace. + If set to false, then jpy produces exceptions using only the underlying Java exception's toString method. .. py:data:: diag :module: jpy @@ -379,7 +393,7 @@ Java object types | ``java.lang.String`` | 1 | 0 | 0 | 0 | 100 | +-------------------------+--------------+----------+---------+------------+---------+ | ``java.lang.Object`` | 1 | 10 | 10 | 10 | 10 | -+-------------------------+--------------+----------+---------+------------+---------+ ++-------------------------+--------------+----------+---------+------------+---------+jpy Java primitive array types -------------------------- @@ -410,6 +424,9 @@ given above, the a match value of 10 applies, as long as the item size of a buff Java object array types ----------------------- +For String arrays, if a sequence is matched with a value of 80 if all the elements in the sequence are Python strings. + + todo diff --git a/jpyutil.py b/jpyutil.py index ea15f43cc2..7d080c89d2 100644 --- a/jpyutil.py +++ b/jpyutil.py @@ -32,8 +32,8 @@ import subprocess -__author__ = "Norman Fomferra, Brockmann Consult GmbH" -__copyright__ = "Copyright 2015-2017 Brockmann Consult GmbH" +__author__ = "Norman Fomferra (Brockmann Consult GmbH) and contributors" +__copyright__ = "Copyright 2015-2018 Brockmann Consult GmbH and contributors" __license__ = "Apache 2.0" __version__ = "0.9.1.dev1" diff --git a/pom.xml b/pom.xml index fc7ae1f646..c587e9d8c8 100644 --- a/pom.xml +++ b/pom.xml @@ -188,4 +188,4 @@ - \ No newline at end of file + diff --git a/setup.py b/setup.py index 0aea809b08..c9d1ea74b3 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ sources = [ os.path.join(src_main_c_dir, 'jpy_module.c'), os.path.join(src_main_c_dir, 'jpy_diag.c'), + os.path.join(src_main_c_dir, 'jpy_verboseexcept.c'), os.path.join(src_main_c_dir, 'jpy_conv.c'), os.path.join(src_main_c_dir, 'jpy_compat.c'), os.path.join(src_main_c_dir, 'jpy_jtype.c'), @@ -98,6 +99,7 @@ os.path.join(src_test_py_dir, 'jpy_typeconv_test.py'), os.path.join(src_test_py_dir, 'jpy_typeres_test.py'), os.path.join(src_test_py_dir, 'jpy_modretparam_test.py'), + os.path.join(src_test_py_dir, 'jpy_translation_test.py'), os.path.join(src_test_py_dir, 'jpy_gettype_test.py'), ] @@ -200,7 +202,7 @@ def test_python_java_classes(): """ Run Python tests against JPY test classes """ sub_env = {'PYTHONPATH': _build_dir()} - log.info('Executing Python unit tests (against Java runtime classes)...') + log.info('Executing Python unit tests (against JPY test classes)...') return jpyutil._execute_python_scripts(python_java_jpy_tests, env=sub_env) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index ef2a05056f..53cd7ef72d 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ #include @@ -32,7 +35,12 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyValue, jstring jName); PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyValue, jboolean isMethodCall, jstring jName, jint argCount, jobjectArray jArgs, jobjectArray jParamClasses); void PyLib_HandlePythonException(JNIEnv* jenv); +void PyLib_ThrowOOM(JNIEnv* jenv); +void PyLib_ThrowFNFE(JNIEnv* jenv, const char *file); +void PyLib_ThrowUOE(JNIEnv* jenv, const char *message); +void PyLib_ThrowRTE(JNIEnv* jenv, const char *message); void PyLib_RedirectStdOut(void); +int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap); static int JPy_InitThreads = 0; @@ -117,6 +125,73 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning return init && JPy_Module != NULL; } +#define MAX_PYTHON_HOME 256 +#if defined(JPY_COMPAT_33P) +wchar_t staticPythonHome[MAX_PYTHON_HOME]; +#elif defined(JPY_COMPAT_27) +char staticPythonHome[MAX_PYTHON_HOME]; +#endif + +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome) +{ +#if defined(JPY_COMPAT_33P) && !defined(JPY_COMPAT_35P) + return 0; // Not supported because DecodeLocale didn't exist in 3.4 +#else + + #if defined(JPY_COMPAT_35P) + const wchar_t* pythonHome = NULL; + #elif defined(JPY_COMPAT_27) + const char* pythonHome = NULL; + #endif + + const char *nonWidePythonHome = NULL; + jboolean result = 0; + nonWidePythonHome = (*jenv)->GetStringUTFChars(jenv, jPythonHome, NULL); + + if (nonWidePythonHome != NULL) { + + #if defined(JPY_COMPAT_35P) + pythonHome = Py_DecodeLocale(nonWidePythonHome, NULL); + if (pythonHome != NULL) { + if (wcslen(pythonHome) < MAX_PYTHON_HOME) { + wcscpy(staticPythonHome, pythonHome); + result = 1; + } + else { + PyMem_RawFree(pythonHome); + } + + } + + #elif defined(JPY_COMPAT_27) + pythonHome = nonWidePythonHome; + if (strlen(pythonHome) < MAX_PYTHON_HOME) { + strcpy(staticPythonHome, pythonHome); + result = 1; + } + #endif + + if (result) { + Py_SetPythonHome(staticPythonHome); + + #if defined(JPY_COMPAT_35P) + PyMem_RawFree(pythonHome); + #endif + } + + (*jenv)->ReleaseStringUTFChars(jenv, jPythonHome, nonWidePythonHome); + } + + return result; +#endif +} + /* * Class: org_jpy_PyLib * Method: startPython0 @@ -291,18 +366,26 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript (JNIEnv* jenv, jclass jLibClass, jstring jScript) { const char* scriptChars; - int retCode; + int retCode = -1; JPy_BEGIN_GIL_STATE scriptChars = (*jenv)->GetStringUTFChars(jenv, jScript, NULL); + if (scriptChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_execScript: script='%s'\n", scriptChars); retCode = PyRun_SimpleString(scriptChars); if (retCode < 0) { JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_execScript: error: PyRun_SimpleString(\"%s\") returned %d\n", scriptChars, retCode); // Note that we cannot retrieve last Python exception after a calling PyRun_SimpleString, see documentation of PyRun_SimpleString. } - (*jenv)->ReleaseStringUTFChars(jenv, jScript, scriptChars); + +error: + if (scriptChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jScript, scriptChars); + } JPy_END_GIL_STATE @@ -319,7 +402,7 @@ PyObject* PyLib_ConvertJavaToPythonObject(JNIEnv* jenv, jobject jObject) int PyLib_ConvertPythonToJavaObject(JNIEnv* jenv, PyObject* pyObject, jobject* jObject) { - return JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, pyObject, jObject); + return JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, pyObject, jObject, JNI_FALSE); } void dumpDict(const char* dictName, PyObject* dict) @@ -329,6 +412,11 @@ void dumpDict(const char* dictName, PyObject* dict) Py_ssize_t pos = 0; Py_ssize_t i = 0; + if (!PyDict_Check(dict)) { + printf(">> dumpDict: %s is not a dictionary!\n", dictName); + return; + } + size = PyDict_Size(dict); printf(">> dumpDict: %s.size = %ld\n", dictName, size); while (PyDict_Next(dict, &pos, &key, &value)) { @@ -339,231 +427,953 @@ void dumpDict(const char* dictName, PyObject* dict) } } - -JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode - (JNIEnv* jenv, jclass jLibClass, jstring jCode, jint jStart, jobject jGlobals, jobject jLocals) -{ - const char* codeChars; - PyObject* pyReturnValue; - PyObject* pyGlobals; - PyObject* pyLocals; +/** + * Get the globals from the __main__ module. + */ +PyObject *getMainGlobals() { PyObject* pyMainModule; + PyObject* pyGlobals; + + JPy_BEGIN_GIL_STATE + + pyMainModule = PyImport_AddModule("__main__"); // borrowed ref + + if (pyMainModule == NULL) { + return NULL; + } + + + pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref + + JPy_END_GIL_STATE + + return pyGlobals; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getMainGlobals + (JNIEnv *jenv, jclass libClass) { + jobject objectRef; + + PyObject *globals = getMainGlobals(); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, globals, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_copyDict + (JNIEnv *jenv, jclass libClass, jlong pyPointer) { + jobject objectRef; + PyObject *src, *copy; + + src = (PyObject*)pyPointer; + + if (!PyDict_Check(src)) { + PyLib_ThrowUOE(jenv, "Not a dictionary!"); + return NULL; + } + + copy = PyDict_Copy(src); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, copy, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_newDict + (JNIEnv *jenv, jclass libClass) { + jobject objectRef; + PyObject *dict; + + dict = PyDict_New(); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, dict, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +/** + * Copies a Java Map into a new Python dictionary. + */ +PyObject *copyJavaStringObjectMapToPyDict(JNIEnv *jenv, jobject jMap) { + PyObject *result; + jobject entrySet, iterator, mapEntry; + jboolean hasNext; + + result = PyDict_New(); + if (result == NULL) { + return result; + } + + entrySet = (*jenv)->CallObjectMethod(jenv, jMap, JPy_Map_entrySet_MID); + if (entrySet == NULL) { + goto error; + } + + iterator = (*jenv)->CallObjectMethod(jenv, entrySet, JPy_Set_Iterator_MID); + if (iterator == NULL) { + goto error; + } + + hasNext = (*jenv)->CallBooleanMethod(jenv, iterator, JPy_Iterator_hasNext_MID); + + while (hasNext) { + jobject key, value; + char const *keyChars; + PyObject *pyKey; + PyObject *pyValue; + JPy_JType* type; + + mapEntry = (*jenv)->CallObjectMethod(jenv, iterator, JPy_Iterator_next_MID); + if (mapEntry == NULL) { + goto error; + } + + key = (*jenv)->CallObjectMethod(jenv, mapEntry, JPy_Map_Entry_getKey_MID); + if (key == NULL) { + goto error; + } + + // we require string keys + if (!(*jenv)->IsInstanceOf(jenv, key, JPy_String_JClass)) { + goto error; + } + keyChars = (*jenv)->GetStringUTFChars(jenv, (jstring)key, NULL); + if (keyChars == NULL) { + goto error; + } + + pyKey = JPy_FROM_CSTR(keyChars); + (*jenv)->ReleaseStringUTFChars(jenv, (jstring)key, keyChars); + + value = (*jenv)->CallObjectMethod(jenv, mapEntry, JPy_Map_Entry_getValue_MID); + + type = JType_GetTypeForObject(jenv, value); + pyValue = JType_ConvertJavaToPythonObject(jenv, type, value); + + PyDict_SetItem(result, pyKey, pyValue); + + hasNext = (*jenv)->CallBooleanMethod(jenv, iterator, JPy_Iterator_hasNext_MID); + } + + return result; + +error: + if (result != NULL) { + Py_XDECREF(result); + } + return NULL; +} + +int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap) { + PyObject *pyKey, *pyValue; + Py_ssize_t pos = 0; + Py_ssize_t dictSize; + jobject *jValues = NULL; + jobject *jKeys = NULL; + int ii; + jboolean exceptionAlready = JNI_FALSE; + jthrowable savedException = NULL; + int retcode = -1; + + if (!PyDict_Check(pyDict)) { + PyLib_ThrowUOE(jenv, "PyObject is not a dictionary!"); + return -1; + } + + dictSize = PyDict_Size(pyDict); + + jKeys = malloc(dictSize * sizeof(jobject)); + jValues = malloc(dictSize * sizeof(jobject)); + if (jKeys == NULL || jValues == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + + exceptionAlready = (*jenv)->ExceptionCheck(jenv); + if (exceptionAlready) { + // save the exception away, because otherwise the conversion methods might spuriously fail + savedException = (*jenv)->ExceptionOccurred(jenv); + (*jenv)->ExceptionClear(jenv); + } + + // first convert everything + ii = 0; + while (PyDict_Next(pyDict, &pos, &pyKey, &pyValue)) { + if (JPy_AsJObjectWithClass(jenv, pyKey, &(jKeys[ii]), JPy_String_JClass) < 0) { + // an error occurred + goto error; + } + if (JPy_AsJObject(jenv, pyValue, &(jValues[ii]), JNI_TRUE) < 0) { + // an error occurred + goto error; + } + ii++; + } + + // now that we've converted, clear out the map and repopulate it + (*jenv)->CallVoidMethod(jenv, jMap, JPy_Map_clear_MID); + for (ii = 0; ii < dictSize; ++ii) { + // since the map is cleared, we want to plow through all of the put operations + (*jenv)->CallObjectMethod(jenv, jMap, JPy_Map_put_MID, jKeys[ii], jValues[ii]); + } + // and we are successful! + retcode = 0; + +error: + if (exceptionAlready) { + // restore our original exception + (*jenv)->Throw(jenv, savedException); + } + + free(jKeys); + free(jValues); + return retcode; +} + +typedef PyObject * (*DoRun)(const void *,int,PyObject*,PyObject*); + +/** + * After setting up the globals and locals, calls the provided function. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + * + */ +jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlobals, jobject jLocals, DoRun runFunction, void *runArg) { + PyObject *pyReturnValue; + PyObject *pyGlobals; + PyObject *pyLocals; int start; + jboolean decGlobals, decLocals, copyGlobals, copyLocals; JPy_BEGIN_GIL_STATE + decGlobals = decLocals = JNI_FALSE; + copyGlobals = copyLocals = JNI_FALSE; pyGlobals = NULL; pyLocals = NULL; pyReturnValue = NULL; - pyMainModule = NULL; - codeChars = NULL; - pyMainModule = PyImport_AddModule("__main__"); // borrowed ref - if (pyMainModule == NULL) { + if (jGlobals == NULL) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using main globals\n"); + pyGlobals = getMainGlobals(); + if (pyGlobals == NULL) { + PyLib_HandlePythonException(jenv); + goto error; + } + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_PyObject_JClass)) { + // if we are an instance of PyObject, just use the object + pyGlobals = (PyObject *)((*jenv)->CallLongMethod(jenv, jGlobals, JPy_PyObject_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyObject globals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_PyDictWrapper_JClass)) { + // if we are an instance of a wrapped dictionary, just use the underlying dictionary + pyGlobals = (PyObject *)((*jenv)->CallLongMethod(jenv, jGlobals, JPy_PyDictWrapper_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyDictWrapper globals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_Map_JClass)) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using Java Map globals\n"); + // this is a java Map and we need to convert it + pyGlobals = copyJavaStringObjectMapToPyDict(jenv, jGlobals); + if (pyGlobals == NULL) { + PyLib_ThrowRTE(jenv, "Could not convert globals from Java Map to Python dictionary"); + goto error; + } + copyGlobals = decGlobals = JNI_TRUE; + } else { + PyLib_ThrowUOE(jenv, "Unsupported globals type"); + goto error; + } + + // if we have no locals specified, using globals for it matches the behavior of eval, "The expression argument is + // parsed and evaluated as a Python expression (technically speaking, a condition list) using the globals and + // locals dictionaries as global and local namespace. ... If the locals dictionary is omitted it defaults to the + // globals dictionary. If both dictionaries are omitted, the expression is executed in the environment where eval() + // is called. The return value is the result of the evaluated expression. + if (jLocals == NULL) { + pyLocals = pyGlobals; + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using globals for locals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_PyObject_JClass)) { + // if we are an instance of PyObject, just use the object + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyObject locals\n"); + pyLocals = (PyObject *)((*jenv)->CallLongMethod(jenv, jLocals, JPy_PyObject_GetPointer_MID)); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_PyDictWrapper_JClass)) { + // if we are an instance of a wrapped dictionary, just use the underlying dictionary + pyLocals = (PyObject *)((*jenv)->CallLongMethod(jenv, jLocals, JPy_PyDictWrapper_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyDictWrapper locals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_Map_JClass)) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using Java Map locals\n"); + // this is a java Map and we need to convert it + pyLocals = copyJavaStringObjectMapToPyDict(jenv, jLocals); + if (pyLocals == NULL) { + PyLib_ThrowRTE(jenv, "Could not convert locals from Java Map to Python dictionary"); + goto error; + } + copyLocals = decLocals = JNI_TRUE; + } else { + PyLib_ThrowUOE(jenv, "Unsupported locals type"); + goto error; + } + + start = jStart == JPy_IM_STATEMENT ? Py_single_input : + jStart == JPy_IM_SCRIPT ? Py_file_input : + Py_eval_input; + + pyReturnValue = runFunction(runArg, start, pyGlobals, pyLocals); + if (pyReturnValue == NULL) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: Handle Python Exception\n"); PyLib_HandlePythonException(jenv); goto error; } +error: + if (copyGlobals) { + copyPythonDictToJavaMap(jenv, pyGlobals, jGlobals); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: copied back Java global\n"); + } + if (copyLocals) { + copyPythonDictToJavaMap(jenv, pyLocals, jLocals); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: copied back Java locals\n"); + } + if (decGlobals) { + Py_XDECREF(pyGlobals); + } + if (decLocals) { + Py_XDECREF(pyLocals); + } + + JPy_END_GIL_STATE + + return (jlong) pyReturnValue; +} + +PyObject *pyRunStringWrapper(const char *code, int start, PyObject *globals, PyObject *locals) { + PyObject *result = PyRun_String(code, start, globals, locals); + return result; +} + +/** + * Calls PyRun_String under the covers to execute the string contents. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + */ +JNIEXPORT +jlong JNICALL Java_org_jpy_PyLib_executeCode + (JNIEnv* jenv, jclass jLibClass, jstring jCode, jint jStart, jobject jGlobals, jobject jLocals) { + const char *codeChars; + jlong result; + codeChars = (*jenv)->GetStringUTFChars(jenv, jCode, NULL); if (codeChars == NULL) { - // todo: Throw out-of-memory error + PyLib_ThrowOOM(jenv); + return NULL; + } + + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeCode: code='%s'\n", codeChars); + + result = executeInternal(jenv, jLibClass, jStart, jGlobals, jLocals, (DoRun)pyRunStringWrapper, codeChars); + + if (codeChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jCode, codeChars); + } + + return result; +} + +typedef struct { + FILE *fp; + const char *filechars; +} RunFileArgs; + +PyObject *pyRunFileWrapper(RunFileArgs *args, int start, PyObject *globals, PyObject *locals) { + return PyRun_File(args->fp, args->filechars, start, globals, locals); +} + +/** + * Calls PyRun_Script under the covers to execute the script contents. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) { + RunFileArgs runFileArgs; + jlong result = 0; + + runFileArgs.fp = NULL; + runFileArgs.filechars = NULL; + + runFileArgs.filechars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); + if (runFileArgs.filechars == NULL) { + PyLib_ThrowOOM(jenv); goto error; } - pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref - if (pyGlobals == NULL) { - PyLib_HandlePythonException(jenv); + runFileArgs.fp = fopen(runFileArgs.filechars, "r"); + if (!runFileArgs.fp) { + PyLib_ThrowFNFE(jenv, runFileArgs.filechars); goto error; } - pyLocals = PyDict_New(); // new ref - if (pyLocals == NULL) { + result = executeInternal(jenv, jLibClass, jStart, jGlobals, jLocals, (DoRun)pyRunFileWrapper, &runFileArgs); + +error: + if (runFileArgs.filechars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jFile, runFileArgs.filechars); + } + if (runFileArgs.fp != NULL) { + fclose(runFileArgs.fp); + } + return result; +} + +/* + * Class: org_jpy_python_PyLib + * Method: incRef + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_incRef + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + Py_ssize_t refCount; + + pyObject = (PyObject*) objId; + + if (Py_IsInitialized()) { + JPy_BEGIN_GIL_STATE + + refCount = pyObject->ob_refcnt; + JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_incRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); + Py_INCREF(pyObject); + + JPy_END_GIL_STATE + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_incRef: error: no interpreter: pyObject=%p\n", pyObject); + } +} + +/* + * Class: org_jpy_python_PyLib + * Method: decRef + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + Py_ssize_t refCount; + + pyObject = (PyObject*) objId; + + if (Py_IsInitialized()) { + JPy_BEGIN_GIL_STATE + + refCount = pyObject->ob_refcnt; + if (refCount <= 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: refCount <= 0: pyObject=%p, refCount=%d\n", pyObject, refCount); + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_decRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); + Py_DECREF(pyObject); + } + + JPy_END_GIL_STATE + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: no interpreter: pyObject=%p\n", pyObject); + } +} + + +/* + * Class: org_jpy_python_PyLib + * Method: getIntValue + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jint value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + value = (jint) JPy_AS_CLONG(pyObject); + + JPy_END_GIL_STATE + + return value; +} + +/** + * Used to convert a python object into it's corresponding boolean. If the PyObject is not a boolean; + * then return false. + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_getBooleanValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jboolean value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + if (PyBool_Check(pyObject)) { + value = (pyObject == Py_True) ? JNI_TRUE : JNI_FALSE; + } else { + value = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return value; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getDoubleValue + * Signature: (J)D + */ +JNIEXPORT jdouble JNICALL Java_org_jpy_PyLib_getDoubleValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jdouble value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + value = (jdouble) PyFloat_AsDouble(pyObject); + + JPy_END_GIL_STATE + + return value; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getStringValue + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jstring jString; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + if (JPy_AsJString(jenv, pyObject, &jString) < 0) { + jString = NULL; + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getStringValue: error: failed to convert Python object to Java String\n"); PyLib_HandlePythonException(jenv); - goto error; } - // todo for https://github.com/bcdev/jpy/issues/53 - // - copy jGlobals into pyGlobals (convert Java --> Python values) - // - copy jLocals into pyLocals (convert Java --> Python values) + JPy_END_GIL_STATE + + return jString; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getObjectValue + * Signature: (J)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jobject jObject; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + if (JObj_Check(pyObject)) { + jObject = ((JPy_JObj*) pyObject)->objectRef; + } else { + if (JPy_AsJObject(jenv, pyObject, &jObject, JNI_FALSE) < 0) { + jObject = NULL; + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectValue: error: failed to convert Python object to Java Object\n"); + PyLib_HandlePythonException(jenv); + } + } + + JPy_END_GIL_STATE + + return jObject; +} + +/** + * Returns true if this object can be converted from a Python object into a Java object (or primitive); + * if this returns false, when you fetch an Object from Python it will be a PyObject wrapper. + * + * objId is a pointer to a PyObject. + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jboolean result; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + result = pyObject == Py_None || JObj_Check(pyObject) || PyBool_Check(pyObject) || + JPy_IS_CLONG(pyObject) || PyFloat_Check(pyObject) || JPy_IS_STR(pyObject) ? JNI_TRUE : JNI_FALSE; + + + JPy_END_GIL_STATE + + return result; +} + +/** + * Gets the Python type object of specified objId. + * + * objId is a pointer to a PyObject. + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + + JPy_BEGIN_GIL_STATE + + pyObject = ((PyObject*) objId)->ob_type; + + JPy_END_GIL_STATE + + return (jlong)pyObject; +} + +/** + * Evaluate PyDict_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @return true if objId is a python dictionary + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyDict_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +/** + * Evaluate PyList_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @return true if objId is a python list + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyList_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE - start = jStart == JPy_IM_STATEMENT ? Py_single_input : - jStart == JPy_IM_SCRIPT ? Py_file_input : - Py_eval_input; + return result; +} - pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyLocals); - if (pyReturnValue == NULL) { - PyLib_HandlePythonException(jenv); - goto error; - } +/** + * Evaluate PyBool_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python boolean + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; - // todo for https://github.com/bcdev/jpy/issues/53 - // - copy pyGlobals into jGlobals (convert Python --> Java values) - // - copy pyLocals into jLocals (convert Python --> Java values) - //dumpDict("pyGlobals", pyGlobals); - //dumpDict("pyLocals", pyLocals); + JPy_BEGIN_GIL_STATE -error: - if (codeChars != NULL) { - (*jenv)->ReleaseStringUTFChars(jenv, jCode, codeChars); + if (PyBool_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; } - Py_XDECREF(pyLocals); - JPy_END_GIL_STATE - return (jlong) pyReturnValue; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: incRef - * Signature: (J)V +/** + * Check equality against Py_None and a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a None */ -JNIEXPORT void JNICALL Java_org_jpy_PyLib_incRef +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - Py_ssize_t refCount; + jboolean result; - pyObject = (PyObject*) objId; - - if (Py_IsInitialized()) { - JPy_BEGIN_GIL_STATE - - refCount = pyObject->ob_refcnt; - JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_incRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); - Py_INCREF(pyObject); + JPy_BEGIN_GIL_STATE - JPy_END_GIL_STATE + if (Py_None == (((PyObject*) objId))) { + result = JNI_TRUE; } else { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_incRef: error: no interpreter: pyObject=%p\n", pyObject); + result = JNI_FALSE; } + + JPy_END_GIL_STATE + + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: decRef - * Signature: (J)V +/** + * Evaluate PyInt_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python int */ -JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - Py_ssize_t refCount; + int check; - pyObject = (PyObject*) objId; + JPy_BEGIN_GIL_STATE - if (Py_IsInitialized()) { - JPy_BEGIN_GIL_STATE +#ifdef JPY_COMPAT_27 + check = PyInt_Check(((PyObject*) objId)); +#else + check = JPy_IS_CLONG(((PyObject*) objId)); +#endif - refCount = pyObject->ob_refcnt; - if (refCount <= 0) { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: refCount <= 0: pyObject=%p, refCount=%d\n", pyObject, refCount); - } else { - JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_decRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); - Py_DECREF(pyObject); - } + JPy_END_GIL_STATE - JPy_END_GIL_STATE + return check ? (jboolean)JNI_TRUE : (jboolean)JNI_FALSE; +} + +/** + * Evaluate PyLong_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python long + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyLong_Check(((PyObject*) objId))) { + result = JNI_TRUE; } else { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: no interpreter: pyObject=%p\n", pyObject); + result = JNI_FALSE; } -} + JPy_END_GIL_STATE -/* - * Class: org_jpy_python_PyLib - * Method: getIntValue - * Signature: (J)I + return result; +} + +/** + * Evaluate PyFloat_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python float */ -JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jint value; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; - value = (jint) JPy_AS_CLONG(pyObject); + if (PyFloat_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } JPy_END_GIL_STATE - return value; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getDoubleValue - * Signature: (J)D +/** + * Evaluate PyString_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python String */ -JNIEXPORT jdouble JNICALL Java_org_jpy_PyLib_getDoubleValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jdouble value; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; - value = (jdouble) PyFloat_AsDouble(pyObject); + if (JPy_IS_STR(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } JPy_END_GIL_STATE - return value; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getStringValue - * Signature: (J)Ljava/lang/String; +/** + * Evaluate PyCallable_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python callable */ -JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jstring jString; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; + if (PyCallable_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } - if (JPy_AsJString(jenv, pyObject, &jString) < 0) { - jString = NULL; - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getStringValue: error: failed to convert Python object to Java String\n"); - PyLib_HandlePythonException(jenv); + JPy_END_GIL_STATE + + return result; +} + +/** + * Runs the str function on a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the class object for PyLib + * @param objId a pointer to a python object + * @return the Python toString of this object + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; + jobject jObject; + PyObject *pyStr; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject *) objId; + + pyStr = PyObject_Str(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); + Py_DECREF(pyStr); + } else { + jObject = NULL; } + JPy_END_GIL_STATE - return jString; + return jObject; } -/* - * Class: org_jpy_python_PyLib - * Method: getObjectValue - * Signature: (J)Ljava/lang/Object; + +/** + * Runs the repr function on a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the class object for PyLib + * @param objId a pointer to a python object + * @return the Python representation string of this object */ -JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue - (JNIEnv* jenv, jclass jLibClass, jlong objId) -{ - PyObject* pyObject; +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; jobject jObject; + PyObject *pyStr; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; + pyObject = (PyObject *) objId; - if (JObj_Check(pyObject)) { - jObject = ((JPy_JObj*) pyObject)->objectRef; + pyStr = PyObject_Repr(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); + Py_DECREF(pyStr); } else { - if (JPy_AsJObject(jenv, pyObject, &jObject) < 0) { - jObject = NULL; - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectValue: error: failed to convert Python object to Java Object\n"); - PyLib_HandlePythonException(jenv); - } + jObject = NULL; } + JPy_END_GIL_STATE return jObject; @@ -604,7 +1414,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_jpy_PyLib_getObjectArrayValue jObject = NULL; goto error; } - if (JPy_AsJObject(jenv, pyItem, &jItem) < 0) { + if (JPy_AsJObject(jenv, pyItem, &jItem, JNI_FALSE) < 0) { (*jenv)->DeleteLocalRef(jenv, jObject); jObject = NULL; JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectArrayValue: error: failed to convert Python item to Java Object\n"); @@ -640,12 +1450,16 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_importModule (JNIEnv* jenv, jclass jLibClass, jstring jName) { PyObject* pyName; - PyObject* pyModule; + PyObject* pyModule = NULL; const char* nameChars; JPy_BEGIN_GIL_STATE nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_importModule: name='%s'\n", nameChars); /* Note: pyName is a new reference */ pyName = JPy_FROM_CSTR(nameChars); @@ -655,7 +1469,11 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_importModule PyLib_HandlePythonException(jenv); } Py_XDECREF(pyName); - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } JPy_END_GIL_STATE @@ -739,6 +1557,10 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue pyObject = (PyObject*) objId; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_setAttributeValue: objId=%p, name='%s', jValue=%p, jValueClass=%p\n", pyObject, nameChars, jValue, jValueClass); if (jValueClass != NULL) { @@ -766,9 +1588,92 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue } error: - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } + + JPy_END_GIL_STATE +} + +/** + * Deletes an attribute from an object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @param jName the java string naming the attribute to delete + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute + (JNIEnv* jenv, jclass jLibClass, jlong objId, jstring jName) +{ + PyObject* pyObject; + const char* nameChars; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_delAttribute: objId=%p, name='%s'\n", pyObject, nameChars); + + if (PyObject_DelAttrString(pyObject, nameChars) < 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_delAttribute: error: PyObject_DelAttrString failed on attribute '%s'\n", nameChars); + PyLib_HandlePythonException(jenv); + goto error; + } + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } + + + JPy_END_GIL_STATE +} + +/* + * Checks for an attribute's existence. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @param jName the java string naming the attribute to delete + * + * @return true if the attribute exists on this object + */ + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute + (JNIEnv* jenv, jclass jLibClass, jlong objId, jstring jName) +{ + PyObject* pyObject; + const char* nameChars; + jboolean result = JNI_FALSE; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_delAttribute: objId=%p, name='%s'\n", pyObject, nameChars); + + result = PyObject_HasAttrString(pyObject, nameChars) ? JNI_TRUE : JNI_FALSE; + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } JPy_END_GIL_STATE + + return result; } @@ -859,10 +1764,14 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_00024Diag_setFlags PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyObject, jstring jName) { - PyObject* pyValue; + PyObject* pyValue = NULL; const char* nameChars; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "PyLib_GetAttributeObject: objId=%p, name='%s'\n", pyObject, nameChars); /* Note: pyValue is a new reference */ pyValue = PyObject_GetAttrString(pyObject, nameChars); @@ -870,16 +1779,19 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyObject, jstring jNa JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "PyLib_GetAttributeObject: error: attribute not found '%s'\n", nameChars); PyLib_HandlePythonException(jenv); } - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } return pyValue; } PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean isMethodCall, jstring jName, jint argCount, jobjectArray jArgs, jobjectArray jParamClasses) { - PyObject* pyCallable; - PyObject* pyArgs; + PyObject* pyCallable = NULL; + PyObject* pyArgs = NULL; PyObject* pyArg; - PyObject* pyReturnValue; + PyObject* pyReturnValue = Py_None; const char* nameChars; jint i; jobject jArg; @@ -889,6 +1801,10 @@ PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean i pyReturnValue = NULL; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "PyLib_CallAndReturnObject: objId=%p, isMethodCall=%d, name='%s', argCount=%d\n", pyObject, isMethodCall, nameChars, argCount); @@ -981,7 +1897,9 @@ PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean i Py_INCREF(pyReturnValue); error: - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } Py_XDECREF(pyCallable); Py_XDECREF(pyArgs); @@ -1051,6 +1969,8 @@ void PyLib_HandlePythonException(JNIEnv* jenv) char* filenameChars = NULL; char* namespaceChars = NULL; + jclass jExceptionClass; + if (PyErr_Occurred() == NULL) { return; } @@ -1064,6 +1984,13 @@ void PyLib_HandlePythonException(JNIEnv* jenv) typeChars = PyLib_ObjToChars(pyType, &pyTypeUtf8); valueChars = PyLib_ObjToChars(pyValue, &pyValueUtf8); + if (PyObject_TypeCheck(pyValue, (PyTypeObject*) PyExc_KeyError)) { + jExceptionClass = JPy_KeyError_JClass; + } else if (PyObject_TypeCheck(pyValue, (PyTypeObject*) PyExc_StopIteration)) { + jExceptionClass = JPy_StopIteration_JClass; + } else { + jExceptionClass = JPy_RuntimeException_JClass; + } if (pyTraceback != NULL) { PyObject* pyFrame = NULL; @@ -1106,13 +2033,13 @@ void PyLib_HandlePythonException(JNIEnv* jenv) linenoChars != NULL ? linenoChars : JPY_NOT_AVAILABLE_MSG, namespaceChars != NULL ? namespaceChars : JPY_NOT_AVAILABLE_MSG, filenameChars != NULL ? filenameChars : JPY_NOT_AVAILABLE_MSG); - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, javaMessage); + (*jenv)->ThrowNew(jenv, jExceptionClass, javaMessage); PyMem_Del(javaMessage); } else { - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, JPY_INFO_ALLOC_FAILED_MSG); + (*jenv)->ThrowNew(jenv, jExceptionClass, JPY_INFO_ALLOC_FAILED_MSG); } } else { - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, JPY_NO_INFO_MSG); + (*jenv)->ThrowNew(jenv, jExceptionClass, JPY_NO_INFO_MSG); } Py_XDECREF(pyType); @@ -1127,6 +2054,40 @@ void PyLib_HandlePythonException(JNIEnv* jenv) PyErr_Clear(); } +/** + * Throw an OutOfMemoryError. + * @param jenv the jni environment + */ +void PyLib_ThrowOOM(JNIEnv* jenv) { + (*jenv)->ThrowNew(jenv, JPy_OutOfMemoryError_JClass, "Out of memory"); +} + +/** + * Throw a FileNotFoundException. + * @param jenv the jni environment + */ +void PyLib_ThrowFNFE(JNIEnv* jenv, const char *file) { + (*jenv)->ThrowNew(jenv, JPy_FileNotFoundException_JClass, file); +} + +/** + * Throw an UnsupportedOperationException. + * @param jenv the jni environment + * @param message the exception message + */ +void PyLib_ThrowUOE(JNIEnv* jenv, const char *message) { + (*jenv)->ThrowNew(jenv, JPy_UnsupportedOperationException_JClass, message); +} + +/** + * Throw an UnsupportedOperationException. + * @param jenv the jni environment + * @param message the exception message + */ +void PyLib_ThrowRTE(JNIEnv* jenv, const char *message) { + (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, message); +} + //////////////////////////////////////////////////////////////////////////////////////////////// // Redirect stdout diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index 33e40bbf7b..6d870fd15f 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning (JNIEnv *, jclass); +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome); + /* * Class: org_jpy_PyLib * Method: startPython0 @@ -49,12 +57,36 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript /* * Class: org_jpy_PyLib - * Method: execute - * Signature: (Ljava/lang/String;ILjava/util/Map;Ljava/util/Map;)J + * Method: executeCode + * Signature: (Ljava/lang/String;ILjava/lang/Object;Ljava/lang/Object;)J */ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode (JNIEnv *, jclass, jstring, jint, jobject, jobject); +/* + * Class: org_jpy_PyLib + * Method: executeScript + * Signature: (Ljava/lang/String;ILjava/lang/Object;Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv *, jclass, jstring, jint, jobject, jobject); + +/* + * Class: org_jpy_PyLib + * Method: getMainGlobals + * Signature: ()Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getMainGlobals + (JNIEnv *, jclass); + +/* + * Class: org_jpy_PyLib + * Method: copyDict + * Signature: (J)Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_copyDict + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: incRef @@ -79,6 +111,14 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue (JNIEnv *, jclass, jlong); +/* + * Class: org_jpy_PyLib + * Method: getBooleanValue + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_getBooleanValue + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: getDoubleValue @@ -103,6 +143,118 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue (JNIEnv *, jclass, jlong); +/* + * Class: org_jpy_PyLib + * Method: isConvertible + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyNoneCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyDictCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyListCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyBoolCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyIntCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyLongCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyFloatCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyStringCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyCallableCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: getType + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: str + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: repr + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: newDict + * Signature: ()Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_newDict + (JNIEnv *, jclass); + /* * Class: org_jpy_PyLib * Method: getObjectArrayValue @@ -143,6 +295,22 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getAttributeValue JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue (JNIEnv *, jclass, jlong, jstring, jobject, jclass); +/* + * Class: org_jpy_PyLib + * Method: delAttribute + * Signature: (JLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute + (JNIEnv *, jclass, jlong, jstring); + +/* + * Class: org_jpy_PyLib + * Method: hasAttribute + * Signature: (JLjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute + (JNIEnv *, jclass, jlong, jstring); + /* * Class: org_jpy_PyLib * Method: callAndReturnObject diff --git a/src/main/c/jpy_compat.h b/src/main/c/jpy_compat.h index 818fb21447..62f969bc88 100644 --- a/src/main/c/jpy_compat.h +++ b/src/main/c/jpy_compat.h @@ -30,6 +30,9 @@ extern "C" { #undef JPY_COMPAT_33P #elif PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3 #define JPY_COMPAT_33P 1 +#if PY_MINOR_VERSION >= 5 +#define JPY_COMPAT_35P 1 +#endif #undef JPY_COMPAT_27 #else #error JPY_VERSION_ERROR @@ -79,4 +82,4 @@ wchar_t* JPy_AsWideCharString_PriorToPy33(PyObject *unicode, Py_ssize_t *size); #ifdef __cplusplus } /* extern "C" */ #endif -#endif /* !JPY_COMPAT_H */ \ No newline at end of file +#endif /* !JPY_COMPAT_H */ diff --git a/src/main/c/jpy_conv.c b/src/main/c/jpy_conv.c index 18880ad55d..f76c352c95 100644 --- a/src/main/c/jpy_conv.c +++ b/src/main/c/jpy_conv.c @@ -23,14 +23,14 @@ -int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef) +int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jboolean allowJavaWrapping) { - return JType_ConvertPythonToJavaObject(jenv, JPy_JObject, pyObj, objectRef); + return JType_ConvertPythonToJavaObject(jenv, JPy_JObject, pyObj, objectRef, allowJavaWrapping); } int JPy_AsJObjectWithType(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, JPy_JType* type) { - return JType_ConvertPythonToJavaObject(jenv, type, pyObj, objectRef); + return JType_ConvertPythonToJavaObject(jenv, type, pyObj, objectRef, JNI_FALSE); } int JPy_AsJObjectWithClass(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jclass classRef) @@ -52,7 +52,7 @@ int JPy_AsJObjectWithClass(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jc return -1; } } else { - if (JPy_AsJObject(jenv, pyObj, objectRef) < 0) { + if (JPy_AsJObject(jenv, pyObj, objectRef, JNI_FALSE) < 0) { return -1; } } diff --git a/src/main/c/jpy_conv.h b/src/main/c/jpy_conv.h index 7a9696737b..f1dfab64db 100644 --- a/src/main/c/jpy_conv.h +++ b/src/main/c/jpy_conv.h @@ -86,8 +86,10 @@ int JPy_AsJString(JNIEnv* jenv, PyObject* pyObj, jstring* stringRef); /** * Convert any Python objects to Java object. + * + * @param allowObjectWrapping if true, may return a PyObject for unrecognized object types */ -int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef); +int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jboolean allowObjectWrapping); /** * Convert Python objects to Java object with known type. diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 050248c0ed..3e6e05cc5d 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -12,6 +12,8 @@ * 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. + * + * This file was modified by Illumon. */ #include "jpy_module.h" @@ -29,6 +31,7 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, JPy_ParamDescriptor* paramDescriptors, JPy_ReturnDescriptor* returnDescriptor, jboolean isStatic, + jboolean isVarArgs, jmethodID mid) { PyTypeObject* type = &JMethod_Type; @@ -41,6 +44,7 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, method->paramDescriptors = paramDescriptors; method->returnDescriptor = returnDescriptor; method->isStatic = isStatic; + method->isVarArgs = isVarArgs; method->mid = mid; Py_INCREF(declaringClass); @@ -84,8 +88,11 @@ void JMethod_Del(JPy_JMethod* method) * Matches the give Python argument tuple against the Java method's formal parameters. * Returns the sum of the i-th argument against the i-th Java parameter. * The maximum match value returned is 100 * method->paramCount. + * + * The isVarArgsArray pointer is set to 1 if this is a varargs match for an object array + * argument. */ -int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* method, int argCount, PyObject* pyArgs) +int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* method, int argCount, PyObject* pyArgs, int *isVarArgArray) { JPy_ParamDescriptor* paramDescriptor; PyObject* pyArg; @@ -93,30 +100,53 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me int matchValue; int i; int i0; + int iLast; + *isVarArgArray = 0; if (method->isStatic) { - //printf("Static! method->paramCount=%d, argCount=%d\n", method->paramCount, argCount); - if (method->paramCount != argCount) { - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); - // argument count mismatch - return 0; - } - if (method->paramCount == 0) { - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: no-argument static method (matchValue=100)\n"); - // There can't be any other static method overloads with no parameters - return 100; - } + if (method->isVarArgs) { + if(argCount < method->paramCount - 1) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: var args argument count mismatch java=%d, python=%d (matchValue=0)\n", method->paramCount, argCount); + // argument count mismatch + return 0; + } + iLast = method->paramCount - 1; + } else { + if (method->paramCount != argCount) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); + // argument count mismatch + return 0; + } + if (method->paramCount == 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: no-argument static method (matchValue=100)\n"); + // There can't be any other static method overloads with no parameters + return 100; + } - i0 = 0; + iLast = argCount; + } matchValueSum = 0; + i0 = 0; } else { PyObject* self; - //printf("Non-Static! method->paramCount=%d, argCount=%d\n", method->paramCount, argCount); - if (method->paramCount != argCount - 1) { + //printf("Non-Static! method->paramCount=%d, argCount=%d, isVarArg=%d\n", method->paramCount, argCount, method->isVarArgs); + + if (method->isVarArgs) { + if (argCount < method->paramCount) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: var args argument count mismatch java=%d, python=%d (matchValue=0)\n", method->paramCount, argCount); + // argument count mismatch + return 0; + } + iLast = method->paramCount; + } + else if (method->paramCount != argCount - 1) { JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); // argument count mismatch return 0; + } else { + iLast = method->paramCount + 1; } + self = PyTuple_GetItem(pyArgs, 0); if (self == Py_None) { JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: self argument is None (matchValue=0)\n"); @@ -141,8 +171,7 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me } paramDescriptor = method->paramDescriptors; - for (i = i0; i < argCount; i++) { - + for (i = i0; i < iLast; i++) { pyArg = PyTuple_GetItem(pyArgs, i); matchValue = paramDescriptor->MatchPyArg(jenv, paramDescriptor, pyArg); @@ -157,6 +186,34 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me matchValueSum += matchValue; paramDescriptor++; } + if (method->isVarArgs) { + int singleMatchValue = 0; + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, i=%d\n", argCount, i); + + if (argCount - i == 0) { + matchValueSum += 10; + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, matchValueSum=%d\n", argCount, method->paramCount, matchValueSum); + } else if (argCount - i == 1) { + // if we have exactly one argument, which matches our array type, then we can use that as an array + pyArg = PyTuple_GetItem(pyArgs, i); + singleMatchValue = paramDescriptor->MatchPyArg(jenv, paramDescriptor, pyArg); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, starting singleMatchValue=%d\n", argCount, method->paramCount, singleMatchValue); + } + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, starting matchValue=%d\n", argCount, method->paramCount, matchValueSum); + matchValue = paramDescriptor->MatchVarArgPyArg(jenv, paramDescriptor, pyArgs, i); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, paramDescriptor->type->javaName='%s', matchValue=%d\n", paramDescriptor->type->javaName, matchValue); + if (matchValue == 0 && singleMatchValue == 0) { + return 0; + } + if (matchValue > singleMatchValue) { + matchValueSum += matchValue; + } else { + matchValueSum += singleMatchValue; + *isVarArgArray = 1; + } + } //printf("JMethod_MatchPyArgs 7\n"); return matchValueSum; @@ -185,7 +242,7 @@ PyObject* JMethod_FromJObject(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArg /** * Invoke a method. We have already ensured that the Python arguments and expected Java parameters match. */ -PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs) +PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, int isVarArgsArray) { jvalue* jArgs; JPy_ArgDisposer* argDisposers; @@ -195,7 +252,7 @@ PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyAr jclass classRef; //printf("JMethod_InvokeMethod 1: typeCode=%c\n", typeCode); - if (JMethod_CreateJArgs(jenv, method, pyArgs, &jArgs, &argDisposers) < 0) { + if (JMethod_CreateJArgs(jenv, method, pyArgs, &jArgs, &argDisposers, isVarArgsArray) < 0) { return NULL; } @@ -325,10 +382,11 @@ PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyAr return returnValue; } -int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jvalue** argValuesRet, JPy_ArgDisposer** argDisposersRet) +int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jvalue** argValuesRet, JPy_ArgDisposer** argDisposersRet, int isVarArgsArray) { JPy_ParamDescriptor* paramDescriptor; - int i, i0, argCount; + Py_ssize_t i, i0, iLast; + Py_ssize_t argCount; PyObject* pyArg; jvalue* jValue; jvalue* jValues; @@ -343,10 +401,17 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva argCount = PyTuple_Size(pyArgs); - i0 = argCount - method->paramCount; - if (!(i0 == 0 || i0 == 1)) { - PyErr_SetString(PyExc_RuntimeError, "internal error"); - return -1; + if (method->isVarArgs) { + // need to know if we expect a self parameter + i0 = method->isStatic ? 0 : 1; + iLast = method->isStatic ? method->paramCount - 1 : method->paramCount; + } else { + i0 = argCount - method->paramCount; + if (!(i0 == 0 || i0 == 1)) { + PyErr_SetString(PyExc_RuntimeError, "internal error"); + return -1; + } + iLast = argCount; } jValues = PyMem_New(jvalue, method->paramCount); @@ -365,7 +430,7 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva paramDescriptor = method->paramDescriptors; jValue = jValues; argDisposer = argDisposers; - for (i = i0; i < argCount; i++) { + for (i = i0; i < iLast; i++) { pyArg = PyTuple_GetItem(pyArgs, i); jValue->l = 0; argDisposer->data = NULL; @@ -379,6 +444,28 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva jValue++; argDisposer++; } + if (method->isVarArgs) { + if (isVarArgsArray) { + pyArg = PyTuple_GetItem(pyArgs, i); + jValue->l = 0; + argDisposer->data = NULL; + argDisposer->DisposeArg = NULL; + if (paramDescriptor->ConvertPyArg(jenv, paramDescriptor, pyArg, jValue, argDisposer) < 0) { + PyMem_Del(jValues); + PyMem_Del(argDisposers); + return -1; + } + } else { + jValue->l = 0; + argDisposer->data = NULL; + argDisposer->DisposeArg = NULL; + if (paramDescriptor->ConvertVarArgPyArg(jenv, paramDescriptor, pyArgs, i, jValue, argDisposer) < 0) { + PyMem_Del(jValues); + PyMem_Del(argDisposers); + return -1; + } + } + } *argValuesRet = jValues; *argDisposersRet = argDisposers; @@ -611,19 +698,22 @@ typedef struct JPy_MethodFindResult JPy_JMethod* method; int matchValue; int matchCount; + int isVarArgsArray; } JPy_MethodFindResult; JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, JPy_MethodFindResult* result) { - int overloadCount; - int argCount; + Py_ssize_t overloadCount; + Py_ssize_t argCount; int matchCount; int matchValue; int matchValueMax; JPy_JMethod* currMethod; JPy_JMethod* bestMethod; int i; + int currentIsVarArgsArray; + int bestIsVarArgsArray; result->method = NULL; result->matchValue = 0; @@ -639,26 +729,34 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* matchCount = 0; matchValueMax = -1; bestMethod = NULL; + bestIsVarArgsArray = 0; - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: method '%s#%s': overloadCount=%d\n", - overloadedMethod->declaringClass->javaName, JPy_AS_UTF8(overloadedMethod->name), overloadCount); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: method '%s#%s': overloadCount=%d, argCount=%d\n", + overloadedMethod->declaringClass->javaName, JPy_AS_UTF8(overloadedMethod->name), overloadCount, argCount); for (i = 0; i < overloadCount; i++) { currMethod = (JPy_JMethod*) PyList_GetItem(overloadedMethod->methodList, i); - matchValue = JMethod_MatchPyArgs(jenv, overloadedMethod->declaringClass, currMethod, argCount, pyArgs); - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: methodList[%d]: paramCount=%d, matchValue=%d\n", i, - currMethod->paramCount, matchValue); + if (currMethod->isVarArgs && matchValueMax > 0 && !bestMethod->isVarArgs) { + // we should not process varargs if we have already found a suitable fixed arity method + break; + } + + matchValue = JMethod_MatchPyArgs(jenv, overloadedMethod->declaringClass, currMethod, argCount, pyArgs, ¤tIsVarArgsArray); + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: methodList[%d]: paramCount=%d, matchValue=%d, isVarArgs=%d\n", i, + currMethod->paramCount, matchValue, currMethod->isVarArgs); if (matchValue > 0) { if (matchValue > matchValueMax) { matchValueMax = matchValue; bestMethod = currMethod; matchCount = 1; + bestIsVarArgsArray = currentIsVarArgsArray; } else if (matchValue == matchValueMax) { matchCount++; } - if (matchValue >= 100 * argCount) { + if (!currMethod->isVarArgs && (matchValue >= 100 * argCount)) { // we can't get any better (if so, we have an internal problem) break; } @@ -668,16 +766,18 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* if (bestMethod == NULL) { matchValueMax = 0; matchCount = 0; + bestIsVarArgsArray = 0; } result->method = bestMethod; result->matchValue = matchValueMax; result->matchCount = matchCount; + result->isVarArgsArray = bestIsVarArgsArray; return bestMethod; } -JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, jboolean visitSuperClass) +JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, jboolean visitSuperClass, int *isVarArgsArray) { JPy_JOverloadedMethod* currentOM; JPy_MethodFindResult result; @@ -700,6 +800,7 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o bestResult.method = NULL; bestResult.matchValue = 0; bestResult.matchCount = 0; + bestResult.isVarArgsArray = 0; currentOM = overloadedMethod; while (1) { @@ -708,8 +809,11 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o return NULL; } if (result.method != NULL) { - if (result.matchValue >= 100 * argCount) { + // in the case where we have a match count that is perfect, but more than one match; the super class might + // have a better match count, because varargs can have fewer arguments than actual parameters. + if (result.matchValue >= 100 * argCount && result.matchCount == 1) { // We can't get any better. + *isVarArgsArray = result.isVarArgsArray; return result.method; } else if (result.matchValue > 0 && result.matchValue > bestResult.matchValue) { // We may have better matching methods overloads in the super class (if any) @@ -740,6 +844,7 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o PyErr_SetString(PyExc_RuntimeError, "ambiguous Java method call, too many matching method overloads found"); return NULL; } else { + *isVarArgsArray = bestResult.isVarArgsArray; return bestResult.method; } } else { @@ -774,7 +879,27 @@ JPy_JOverloadedMethod* JOverloadedMethod_New(JPy_JType* declaringClass, PyObject int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMethod* method) { - return PyList_Append(overloadedMethod->methodList, (PyObject*) method); + Py_ssize_t destinationIndex = -1; + + if (!method->isVarArgs) { + Py_ssize_t ii; + // we need to insert this before the first varargs method + Py_ssize_t size = PyList_Size(overloadedMethod->methodList); + for (ii = 0; ii < size; ii++) { + PyObject *check = PyList_GetItem(overloadedMethod->methodList, ii); + if (((JPy_JMethod *) check)->isVarArgs) { + // this is the first varargs method, so we should go before it + destinationIndex = ii; + break; + } + } + } + + if (destinationIndex >= 0) { + return PyList_Insert(overloadedMethod->methodList, destinationIndex, (PyObject *) method); + } else { + return PyList_Append(overloadedMethod->methodList, (PyObject *) method); + } } /** @@ -795,15 +920,16 @@ PyObject* JOverloadedMethod_call(JPy_JOverloadedMethod* self, PyObject *args, Py { JNIEnv* jenv; JPy_JMethod* method; + int isVarArgsArray; JPy_GET_JNI_ENV_OR_RETURN(jenv, NULL) - method = JOverloadedMethod_FindMethod(jenv, self, args, JNI_TRUE); + method = JOverloadedMethod_FindMethod(jenv, self, args, JNI_TRUE, &isVarArgsArray); if (method == NULL) { return NULL; } - return JMethod_InvokeMethod(jenv, method, args); + return JMethod_InvokeMethod(jenv, method, args, isVarArgsArray); } /** diff --git a/src/main/c/jpy_jmethod.h b/src/main/c/jpy_jmethod.h index 3bcea12e19..8661f07e1b 100644 --- a/src/main/c/jpy_jmethod.h +++ b/src/main/c/jpy_jmethod.h @@ -12,6 +12,8 @@ * 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. + * + * This file was modified by Illumon. */ #ifndef JPY_JMETHOD_H @@ -38,6 +40,8 @@ typedef struct int paramCount; // Method is static? char isStatic; + // Method is varargs? + char isVarArgs; // Method parameter types. Will be NULL, if parameter_count == 0. JPy_ParamDescriptor* paramDescriptors; // Method return type. Will be NULL for constructors. @@ -73,7 +77,7 @@ JPy_JOverloadedMethod; */ extern PyTypeObject JOverloadedMethod_Type; -JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple, jboolean visitSuperClass); +JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple, jboolean visitSuperClass, int *isVarArgsArray); JPy_JMethod* JOverloadedMethod_FindStaticMethod(JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple); JPy_JOverloadedMethod* JOverloadedMethod_New(JPy_JType* declaringClass, PyObject* name, JPy_JMethod* method); int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMethod* method); @@ -84,13 +88,14 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, JPy_ParamDescriptor* paramDescriptors, JPy_ReturnDescriptor* returnDescriptor, jboolean isStatic, + jboolean isVarArgs, jmethodID mid); void JMethod_Del(JPy_JMethod* method); int JMethod_ConvertToJavaValues(JNIEnv* jenv, JPy_JMethod* jMethod, int argCount, PyObject* argTuple, jvalue* jArgs); -int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* jMethod, PyObject* argTuple, jvalue** jValues, JPy_ArgDisposer** jDisposers); +int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* jMethod, PyObject* argTuple, jvalue** jValues, JPy_ArgDisposer** jDisposers, int isVarArgsArray); void JMethod_DisposeJArgs(JNIEnv* jenv, int paramCount, jvalue* jValues, JPy_ArgDisposer* jDisposers); #ifdef __cplusplus diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index a9fda7489c..ab1bd6ca6b 100644 --- a/src/main/c/jpy_jobj.c +++ b/src/main/c/jpy_jobj.c @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" @@ -23,7 +26,7 @@ #include "jpy_jfield.h" #include "jpy_conv.h" -JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef) +PyObject* JObj_New(JNIEnv* jenv, jobject objectRef) { jclass classRef; JPy_JType* type; @@ -38,8 +41,11 @@ JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef) return JObj_FromType(jenv, type, objectRef); } -JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) +PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) { + PyObject* callable; + PyObject* callableResult; + JPy_JObj* obj; obj = (JPy_JObj*) PyObject_New(JPy_JObj, (PyTypeObject*) type); @@ -63,7 +69,21 @@ JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) array->bufferExportCount = 0; } - return obj; + // we check the type translations dictionary for a callable for this java type name, + // and apply the returned callable to the wrapped object + callable = PyDict_GetItemString(JPy_Type_Translations, type->javaName); + if (callable != NULL) { + if (PyCallable_Check(callable)) { + callableResult = PyObject_CallFunction(callable, "OO", type, obj); + if (callableResult == NULL) { + return Py_None; + } else { + return callableResult; + } + } + } + + return (PyObject *)obj; } /** @@ -79,6 +99,7 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) jobject objectRef; jvalue* jArgs; JPy_ArgDisposer* jDisposers; + int isVarArgsArray; JPy_GET_JNI_ENV_OR_RETURN(jenv, -1) @@ -101,12 +122,12 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) return -1; } - jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE); + jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE, &isVarArgsArray); if (jMethod == NULL) { return -1; } - if (JMethod_CreateJArgs(jenv, jMethod, args, &jArgs, &jDisposers) < 0) { + if (JMethod_CreateJArgs(jenv, jMethod, args, &jArgs, &jDisposers, isVarArgsArray) < 0) { return -1; } diff --git a/src/main/c/jpy_jobj.h b/src/main/c/jpy_jobj.h index c7672e757e..e222bee77c 100644 --- a/src/main/c/jpy_jobj.h +++ b/src/main/c/jpy_jobj.h @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ #ifndef JPY_JOBJ_H @@ -37,8 +40,8 @@ JPy_JObj; int JObj_Check(PyObject* arg); -JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef); -JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef); +PyObject* JObj_New(JNIEnv* jenv, jobject objectRef); +PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef); int JObj_InitTypeSlots(PyTypeObject* type, const char* typeName, PyTypeObject* superType); diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 339c897762..78f5e27208 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" @@ -34,7 +37,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type); int JType_AddMethod(JPy_JType* type, JPy_JMethod* method); JPy_ReturnDescriptor* JType_CreateReturnDescriptor(JNIEnv* jenv, jclass returnType); JPy_ParamDescriptor* JType_CreateParamDescriptors(JNIEnv* jenv, int paramCount, jarray paramTypes); -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor); +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean isLastVarArg); void JType_InitMethodParamDescriptorFunctions(JPy_JType* type, JPy_JMethod* method); int JType_ProcessField(JNIEnv* jenv, JPy_JType* declaringType, PyObject* fieldKey, const char* fieldName, jclass fieldClassRef, jboolean isStatic, jboolean isFinal, jfieldID fid); void JType_DisposeLocalObjectRefArg(JNIEnv* jenv, jvalue* value, void* data); @@ -42,6 +45,12 @@ void JType_DisposeReadOnlyBufferArg(JNIEnv* jenv, jvalue* value, void* data); void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data); +static int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedType, int floatMatch); + +static int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedComponentType); + JPy_JType* JType_GetTypeForObject(JNIEnv* jenv, jobject objectRef) { JPy_JType* type; @@ -275,23 +284,23 @@ PyObject* JType_ConvertJavaToPythonObject(JNIEnv* jenv, JPy_JType* type, jobject if (type->componentType == NULL) { // Scalar type, not an array, try to convert to Python equivalent - if (type == JPy_JBooleanObj) { + if (type == JPy_JBooleanObj || type == JPy_JBoolean) { jboolean value = (*jenv)->CallBooleanMethod(jenv, objectRef, JPy_Boolean_BooleanValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JBOOLEAN(value); - } else if (type == JPy_JCharacterObj) { + } else if (type == JPy_JCharacterObj || type == JPy_JChar) { jchar value = (*jenv)->CallCharMethod(jenv, objectRef, JPy_Character_CharValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JCHAR(value); - } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj) { + } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj || type == JPy_JShort || type == JPy_JInt) { jint value = (*jenv)->CallIntMethod(jenv, objectRef, JPy_Number_IntValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JINT(value); - } else if (type == JPy_JLongObj) { + } else if (type == JPy_JLongObj || type == JPy_JLong) { jlong value = (*jenv)->CallLongMethod(jenv, objectRef, JPy_Number_LongValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JLONG(value); - } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj) { + } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj || type == JPy_JFloat || type == JPy_JDouble) { jdouble value = (*jenv)->CallDoubleMethod(jenv, objectRef, JPy_Number_DoubleValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JDOUBLE(value); @@ -439,7 +448,7 @@ int JType_CreateJavaPyObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, job return JType_CreateJavaObject(jenv, type, pyArg, type->classRef, JPy_PyObject_Init_MID, value, objectRef); } -int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef) +int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping) { jint itemCount; jarray arrayRef; @@ -713,7 +722,7 @@ int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyAr (*jenv)->DeleteLocalRef(jenv, arrayRef); return -1; } - if (JType_ConvertPythonToJavaObject(jenv, componentType, pyItem, &jItem) < 0) { + if (JType_ConvertPythonToJavaObject(jenv, componentType, pyItem, &jItem, allowObjectWrapping) < 0) { (*jenv)->DeleteLocalRef(jenv, arrayRef); Py_DECREF(pyItem); return -1; @@ -736,8 +745,7 @@ int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyAr return 0; } - -int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, jobject* objectRef) +int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping) { // Note: There may be a potential memory leak here. // If a new local reference is created in this function and assigned to *objectRef, the reference may escape. @@ -752,9 +760,12 @@ int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyA // If it is already a Java object wrapper JObj, then we are done *objectRef = ((JPy_JObj*) pyArg)->objectRef; return 0; + } else if (JType_Check(pyArg)) { + *objectRef = ((JPy_JType*)pyArg)->classRef; + return 0; } else if (type->componentType != NULL) { // For any other Python argument create a Java object (a new local reference) - return JType_CreateJavaArray(jenv, type->componentType, pyArg, objectRef); + return JType_CreateJavaArray(jenv, type->componentType, pyArg, objectRef, allowObjectWrapping); } else if (type == JPy_JBoolean || type == JPy_JBooleanObj) { return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); } else if (type == JPy_JChar || type == JPy_JCharacterObj) { @@ -773,20 +784,20 @@ int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyA return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); } else if (type == JPy_JPyObject) { return JType_CreateJavaPyObject(jenv, type, pyArg, objectRef); - } else if (type == JPy_JObject) { - if (PyBool_Check(pyArg)) { - return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); - } else if (JPy_IS_CLONG(pyArg)) { - return JType_CreateJavaIntegerObject(jenv, type, pyArg, objectRef); - } else if (PyFloat_Check(pyArg)) { - return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); - } else if (JPy_IS_STR(pyArg)) { - return JPy_AsJString(jenv, pyArg, objectRef); - } - } else if (type == JPy_JString) { - if (JPy_IS_STR(pyArg)) { - return JPy_AsJString(jenv, pyArg, objectRef); - } + } else if (JPy_IS_STR(pyArg) && (type == JPy_JString || type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_JString->classRef, type->classRef)))) { + return JPy_AsJString(jenv, pyArg, objectRef); + } else if (PyBool_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Boolean_JClass, type->classRef)))) { + return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); + } else if (JPy_IS_CLONG(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Integer_JClass, type->classRef)))) { + return JType_CreateJavaIntegerObject(jenv, type, pyArg, objectRef); + } else if (JPy_IS_CLONG(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Long_JClass, type->classRef)))) { + return JType_CreateJavaLongObject(jenv, type, pyArg, objectRef); + } else if (PyFloat_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Double_JClass, type->classRef)))) { + return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); + } else if (PyFloat_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Float_JClass, type->classRef)))) { + return JType_CreateJavaFloatObject(jenv, type, pyArg, objectRef); + } else if (type == JPy_JObject && allowObjectWrapping) { + return JType_CreateJavaPyObject(jenv, JPy_JPyObject, pyArg, objectRef); } return JType_PythonToJavaConversionError(type, pyArg); } @@ -870,7 +881,7 @@ jboolean JType_AcceptMethod(JPy_JType* declaringClass, JPy_JMethod* method) } -int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, const char* methodName, jclass returnType, jarray paramTypes, jboolean isStatic, jmethodID mid) +int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, const char* methodName, jclass returnType, jarray paramTypes, jboolean isStatic, jboolean isVarArgs, jmethodID mid) { JPy_ParamDescriptor* paramDescriptors = NULL; JPy_ReturnDescriptor* returnDescriptor = NULL; @@ -878,7 +889,7 @@ int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, cons JPy_JMethod* method; paramCount = (*jenv)->GetArrayLength(jenv, paramTypes); - JPy_DIAG_PRINT(JPy_DIAG_F_TYPE, "JType_ProcessMethod: methodName=\"%s\", paramCount=%d, isStatic=%d, mid=%p\n", methodName, paramCount, isStatic, mid); + JPy_DIAG_PRINT(JPy_DIAG_F_TYPE, "JType_ProcessMethod: methodName=\"%s\", paramCount=%d, isStatic=%d, isVarArgs=%d, mid=%p\n", methodName, paramCount, isStatic, isVarArgs, mid); if (paramCount > 0) { paramDescriptors = JType_CreateParamDescriptors(jenv, paramCount, paramTypes); @@ -901,7 +912,7 @@ int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, cons returnDescriptor = NULL; } - method = JMethod_New(type, methodKey, paramCount, paramDescriptors, returnDescriptor, isStatic, mid); + method = JMethod_New(type, methodKey, paramCount, paramDescriptors, returnDescriptor, isStatic, isVarArgs, mid); if (method == NULL) { PyMem_Del(paramDescriptors); PyMem_Del(returnDescriptor); @@ -971,6 +982,7 @@ int JType_ProcessClassConstructors(JNIEnv* jenv, JPy_JType* type) jint constrCount; jint i; jboolean isPublic; + jboolean isVarArg; jmethodID mid; PyObject* methodKey; @@ -985,10 +997,11 @@ int JType_ProcessClassConstructors(JNIEnv* jenv, JPy_JType* type) constructor = (*jenv)->GetObjectArrayElement(jenv, constructors, i); modifiers = (*jenv)->CallIntMethod(jenv, constructor, JPy_Constructor_GetModifiers_MID); isPublic = (modifiers & 0x0001) != 0; + isVarArg = (modifiers & 0x0080) != 0; if (isPublic) { parameterTypes = (*jenv)->CallObjectMethod(jenv, constructor, JPy_Constructor_GetParameterTypes_MID); mid = (*jenv)->FromReflectedMethod(jenv, constructor); - JType_ProcessMethod(jenv, type, methodKey, JPy_JTYPE_ATTR_NAME_JINIT, NULL, parameterTypes, 1, mid); + JType_ProcessMethod(jenv, type, methodKey, JPy_JTYPE_ATTR_NAME_JINIT, NULL, parameterTypes, 1, isVarArg, mid); (*jenv)->DeleteLocalRef(jenv, parameterTypes); } (*jenv)->DeleteLocalRef(jenv, constructor); @@ -1065,7 +1078,9 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) jint methodCount; jint i; jboolean isStatic; + jboolean isVarArg; jboolean isPublic; + jboolean isBridge; const char* methodName; jmethodID mid; PyObject* methodKey; @@ -1083,7 +1098,10 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) // see http://docs.oracle.com/javase/6/docs/api/constant-values.html#java.lang.reflect.Modifier.PUBLIC isPublic = (modifiers & 0x0001) != 0; isStatic = (modifiers & 0x0008) != 0; - if (isPublic) { + isVarArg = (modifiers & 0x0080) != 0; + isBridge = (modifiers & 0x0040) != 0; + // we exclude bridge methods; as covariant return types will result in bridge methods that cause ambiguity + if (isPublic && !isBridge) { methodNameStr = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetName_MID); returnType = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetReturnType_MID); parameterTypes = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetParameterTypes_MID); @@ -1091,7 +1109,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) methodName = (*jenv)->GetStringUTFChars(jenv, methodNameStr, NULL); methodKey = Py_BuildValue("s", methodName); - JType_ProcessMethod(jenv, type, methodKey, methodName, returnType, parameterTypes, isStatic, mid); + JType_ProcessMethod(jenv, type, methodKey, methodName, returnType, parameterTypes, isStatic, isVarArg, mid); (*jenv)->ReleaseStringUTFChars(jenv, methodNameStr, methodName); (*jenv)->DeleteLocalRef(jenv, parameterTypes); @@ -1227,7 +1245,7 @@ void JType_InitMethodParamDescriptorFunctions(JPy_JType* type, JPy_JMethod* meth { int index; for (index = 0; index < method->paramCount; index++) { - JType_InitParamDescriptorFunctions(method->paramDescriptors + index); + JType_InitParamDescriptorFunctions(method->paramDescriptors + index, index == method->paramCount - 1 && method->isVarArgs); } } @@ -1352,7 +1370,9 @@ JPy_ParamDescriptor* JType_CreateParamDescriptors(JNIEnv* jenv, int paramCount, paramDescriptor->isOutput = 0; paramDescriptor->isReturn = 0; paramDescriptor->MatchPyArg = NULL; + paramDescriptor->MatchVarArgPyArg = NULL; paramDescriptor->ConvertPyArg = NULL; + paramDescriptor->ConvertVarArgPyArg = NULL; } return paramDescriptors; @@ -1490,6 +1510,336 @@ int JType_MatchPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr return JType_MatchPyArgAsJObject(jenv, paramDescriptor->type, pyArg); } +int JType_MatchVarArgPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType == NULL) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + int matchValue = JType_MatchPyArgAsJObject(jenv, componentType, unpack); + if (matchValue == 0) { + return 0; + } + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + return minMatch; +} + +int JType_MatchVarArgPyArgAsJStringParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != JPy_JString) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + int matchValue = JType_MatchPyArgAsJStringParam(jenv, paramDescriptor, unpack); + if (matchValue == 0) { + return 0; + } + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + return minMatch; +} + +int JType_MatchVarArgPyArgAsJBooleanParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != JPy_JBoolean) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (PyBool_Check(unpack)) matchValue = 100; + else if (JPy_IS_CLONG(unpack)) matchValue = 10; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_MatchVarArgPyArgAsJIntParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JInt); +} + +int JType_MatchVarArgPyArgAsJLongParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JLong); +} + +int JType_MatchVarArgPyArgAsJShortParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JShort); +} + +int JType_MatchVarArgPyArgAsJByteParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JByte); +} + +int JType_MatchVarArgPyArgAsJCharParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JChar); +} + +int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedComponentType) { + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != expectedComponentType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (JPy_IS_CLONG(unpack)) matchValue = 100; + else if (PyBool_Check(unpack)) matchValue = 10; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_MatchVarArgPyArgAsJDoubleParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgAsFPType(paramDescriptor, pyArg, idx, JPy_JDouble, 100); +} + +int JType_MatchVarArgPyArgAsJFloatParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + // float gets a match of 90, so that double has a better chance + return JType_MatchVarArgPyArgAsFPType(paramDescriptor, pyArg, idx, JPy_JFloat, 90); +} + +/* The float and double match functions are almost identical, but for the expected componentType and the match value + * for floating point numbers should give a preference to double over float. */ +int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedType, int floatMatch) { + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != expectedType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (PyFloat_Check(unpack)) matchValue = floatMatch; + else if (PyNumber_Check(unpack)) matchValue = 50; + else if (JPy_IS_CLONG(unpack)) matchValue = 10; + else if (PyBool_Check(unpack)) matchValue = 1; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_ConvertVarArgPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArgOrig, int offset, jvalue* value, JPy_ArgDisposer* disposer) +{ + Py_ssize_t size = PyTuple_Size(pyArgOrig); + PyObject *pyArg = PyTuple_GetSlice(pyArgOrig, offset, size); + + if (pyArg == Py_None) { + // Py_None maps to (Java) NULL + value->l = NULL; + disposer->data = NULL; + disposer->DisposeArg = NULL; + } else if (JObj_Check(pyArg)) { + // If it is a wrapped Java object, it is always a global reference, so don't dispose it + JPy_JObj* obj = (JPy_JObj*) pyArg; + value->l = obj->objectRef; + disposer->data = NULL; + disposer->DisposeArg = NULL; + } else { + // For any other Python argument, we first check if the formal parameter is a primitive array + // and the Python argument is a buffer object + + JPy_JType* paramType = paramDescriptor->type; + JPy_JType* paramComponentType = paramType->componentType; + + if (paramComponentType != NULL && paramComponentType->isPrimitive && PyObject_CheckBuffer(pyArg)) { + Py_buffer* pyBuffer; + int flags; + Py_ssize_t itemCount; + jarray jArray; + void* arrayItems; + jint itemSize; + + pyBuffer = PyMem_New(Py_buffer, 1); + if (pyBuffer == NULL) { + PyErr_NoMemory(); + return -1; + } + + flags = paramDescriptor->isMutable ? PyBUF_WRITABLE : PyBUF_SIMPLE; + if (PyObject_GetBuffer(pyArg, pyBuffer, flags) < 0) { + PyMem_Del(pyBuffer); + return -1; + } + + itemCount = pyBuffer->len / pyBuffer->itemsize; + if (itemCount <= 0) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_Format(PyExc_ValueError, "illegal buffer argument: not a positive item count: %ld", itemCount); + return -1; + } + + if (paramComponentType == JPy_JBoolean) { + jArray = (*jenv)->NewBooleanArray(jenv, itemCount); + itemSize = sizeof(jboolean); + } else if (paramComponentType == JPy_JByte) { + jArray = (*jenv)->NewByteArray(jenv, itemCount); + itemSize = sizeof(jbyte); + } else if (paramComponentType == JPy_JChar) { + jArray = (*jenv)->NewCharArray(jenv, itemCount); + itemSize = sizeof(jchar); + } else if (paramComponentType == JPy_JShort) { + jArray = (*jenv)->NewShortArray(jenv, itemCount); + itemSize = sizeof(jshort); + } else if (paramComponentType == JPy_JInt) { + jArray = (*jenv)->NewIntArray(jenv, itemCount); + itemSize = sizeof(jint); + } else if (paramComponentType == JPy_JLong) { + jArray = (*jenv)->NewLongArray(jenv, itemCount); + itemSize = sizeof(jlong); + } else if (paramComponentType == JPy_JFloat) { + jArray = (*jenv)->NewFloatArray(jenv, itemCount); + itemSize = sizeof(jfloat); + } else if (paramComponentType == JPy_JDouble) { + jArray = (*jenv)->NewDoubleArray(jenv, itemCount); + itemSize = sizeof(jdouble); + } else { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_SetString(PyExc_RuntimeError, "internal error: illegal primitive Java type"); + return -1; + } + + if (pyBuffer->len != itemCount * itemSize) { + Py_ssize_t bufferLen = pyBuffer->len; + Py_ssize_t bufferItemSize = pyBuffer->itemsize; + //printf("%ld, %ld, %ld, %ld\n", pyBuffer->len , pyBuffer->itemsize, itemCount, itemSize); + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_Format(PyExc_ValueError, + "illegal buffer argument: expected size was %ld bytes, but got %ld (expected item size was %d bytes, got %ld)", + itemCount * itemSize, bufferLen, itemSize, bufferItemSize); + return -1; + } + + if (jArray == NULL) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_NoMemory(); + return -1; + } + + if (!paramDescriptor->isOutput) { + arrayItems = (*jenv)->GetPrimitiveArrayCritical(jenv, jArray, NULL); + if (arrayItems == NULL) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_NoMemory(); + return -1; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC|JPy_DIAG_F_MEM, "JType_ConvertPyArgToJObjectArg: moving Python buffer into Java array: pyBuffer->buf=%p, pyBuffer->len=%d\n", pyBuffer->buf, pyBuffer->len); + memcpy(arrayItems, pyBuffer->buf, itemCount * itemSize); + (*jenv)->ReleasePrimitiveArrayCritical(jenv, jArray, arrayItems, 0); + } + + value->l = jArray; + disposer->data = pyBuffer; + disposer->DisposeArg = paramDescriptor->isMutable ? JType_DisposeWritableBufferArg : JType_DisposeReadOnlyBufferArg; + } else { + jobject objectRef; + if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef, JNI_FALSE) < 0) { + return -1; + } + value->l = objectRef; + disposer->data = NULL; + disposer->DisposeArg = JType_DisposeLocalObjectRefArg; + } + } + + return 0; +} + int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyArg) { JPy_JType* argType; @@ -1618,6 +1968,23 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr return matchValue; } } else if (PySequence_Check(pyArg)) { + // if we know the type of the array is a string, we should preferentially match it + if ((*jenv)->IsAssignableFrom(jenv, paramComponentType->classRef, JPy_String_JClass)) { + // it's a string array + Py_ssize_t len = PySequence_Length(pyArg); + Py_ssize_t ii; + + for (ii = 0; ii < len; ++ii) { + PyObject *element = PySequence_GetItem(pyArg, ii); + if (!JPy_IS_STR(element)) { + // if the element is not a string, this is not a good match + return 0; + } + } + + // a String sequence is a good match for a String array + return 80; + } return 10; } } else if (paramType == JPy_JObject) { @@ -1648,6 +2015,33 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr } else if (PyBool_Check(pyArg)) { return 10; } + } else { + if (JPy_IS_STR(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_JString->classRef, paramType->classRef)) { + return 80; + } + } + else if (PyBool_Check(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Boolean_JClass, paramType->classRef)) { + return 80; + } + } + else if (JPy_IS_CLONG(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Integer_JClass, paramType->classRef)) { + return 80; + } + else if ((*jenv)->IsAssignableFrom(jenv, JPy_Long_JClass, paramType->classRef)) { + return 80; + } + } + else if (PyFloat_Check(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Double_JClass, paramType->classRef)) { + return 80; + } + else if ((*jenv)->IsAssignableFrom(jenv, JPy_Float_JClass, paramType->classRef)) { + return 80; + } + } } return 0; @@ -1697,7 +2091,7 @@ int JType_ConvertPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr if (itemCount <= 0) { PyBuffer_Release(pyBuffer); PyMem_Del(pyBuffer); - PyErr_Format(PyExc_ValueError, "illegal buffer argument: negative item count: %ld", itemCount); + PyErr_Format(PyExc_ValueError, "illegal buffer argument: not a positive item count: %ld", itemCount); return -1; } @@ -1769,7 +2163,7 @@ int JType_ConvertPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr disposer->DisposeArg = paramDescriptor->isMutable ? JType_DisposeWritableBufferArg : JType_DisposeReadOnlyBufferArg; } else { jobject objectRef; - if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef) < 0) { + if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef, JNI_FALSE) < 0) { return -1; } value->l = objectRef; @@ -1839,7 +2233,7 @@ void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data) } } -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean isLastVarArg) { JPy_JType* paramType = paramDescriptor->type; @@ -1880,6 +2274,31 @@ void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) paramDescriptor->MatchPyArg = JType_MatchPyArgAsJObjectParam; paramDescriptor->ConvertPyArg = JType_ConvertPyArgToJObjectArg; } + if (isLastVarArg) { + paramDescriptor->ConvertVarArgPyArg = JType_ConvertVarArgPyArgToJObjectArg; + + if (paramType->componentType == JPy_JBoolean) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJBooleanParam; + } else if (paramType->componentType == JPy_JByte) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJByteParam; + } else if (paramType->componentType == JPy_JChar) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJCharParam; + } else if (paramType->componentType == JPy_JShort) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJShortParam; + } else if (paramType->componentType == JPy_JInt) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJIntParam; + } else if (paramType->componentType == JPy_JLong) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJLongParam; + } else if (paramType->componentType == JPy_JFloat) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJFloatParam; + } else if (paramType->componentType == JPy_JDouble) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJDoubleParam; + } else if (paramType->componentType == JPy_JString) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJStringParam; + } else { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJObjectParam; + } + } } /** diff --git a/src/main/c/jpy_jtype.h b/src/main/c/jpy_jtype.h index d8ffb4b90c..83b94df54a 100644 --- a/src/main/c/jpy_jtype.h +++ b/src/main/c/jpy_jtype.h @@ -12,6 +12,10 @@ * 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. + * + * + * This file was modified by Illumon. + * */ #ifndef JPY_JTYPE_H @@ -74,7 +78,9 @@ JPy_ArgDisposer; struct JPy_ParamDescriptor; typedef int (*JPy_MatchPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*); +typedef int (*JPy_MatchVarArgPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, int); typedef int (*JPy_ConvertPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, jvalue*, JPy_ArgDisposer*); +typedef int (*JPy_ConvertVarArgPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, int, jvalue*, JPy_ArgDisposer*); /** * Method return value descriptor. @@ -103,7 +109,9 @@ typedef struct JPy_ParamDescriptor jboolean isOutput; jboolean isReturn; JPy_MatchPyArg MatchPyArg; + JPy_MatchVarArgPyArg MatchVarArgPyArg; JPy_ConvertPyArg ConvertPyArg; + JPy_ConvertVarArgPyArg ConvertVarArgPyArg; } JPy_ParamDescriptor; @@ -115,13 +123,13 @@ JPy_JType* JType_GetTypeForName(JNIEnv* jenv, const char* typeName, jboolean res JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve); PyObject* JType_ConvertJavaToPythonObject(JNIEnv* jenv, JPy_JType* type, jobject objectRef); -int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* arg, jobject* objectRef); +int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* arg, jobject* objectRef, jboolean allowObjectWrapping); PyObject* JType_GetOverloadedMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodName, jboolean useSuperClass); int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg); -int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef); +int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping); // Non-API. Defined in jpy_jobj.c int JType_InitSlots(JPy_JType* type); diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index a5c25df195..fe19389951 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -12,10 +12,14 @@ * 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. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" #include "jpy_diag.h" +#include "jpy_verboseexcept.h" #include "jpy_jtype.h" #include "jpy_jmethod.h" #include "jpy_jfield.h" @@ -85,6 +89,7 @@ static struct PyModuleDef JPy_ModuleDef = PyObject* JPy_Module = NULL; PyObject* JPy_Types = NULL; PyObject* JPy_Type_Callbacks = NULL; +PyObject* JPy_Type_Translations = NULL; PyObject* JException_Type = NULL; // A global reference to a Java VM singleton. @@ -119,6 +124,8 @@ JPy_JType* JPy_JClass = NULL; JPy_JType* JPy_JString = NULL; JPy_JType* JPy_JPyObject = NULL; JPy_JType* JPy_JPyModule = NULL; +JPy_JType* JPy_JThrowable = NULL; +JPy_JType* JPy_JStackTraceElement = NULL; // java.lang.Comparable @@ -161,7 +168,28 @@ jmethodID JPy_Field_GetName_MID = NULL; jmethodID JPy_Field_GetModifiers_MID = NULL; jmethodID JPy_Field_GetType_MID = NULL; +// java.util.Map +jclass JPy_Map_JClass = NULL; +jclass JPy_Map_Entry_JClass = NULL; +jmethodID JPy_Map_entrySet_MID = NULL; +jmethodID JPy_Map_put_MID = NULL; +jmethodID JPy_Map_clear_MID = NULL; +jmethodID JPy_Map_Entry_getKey_MID = NULL; +jmethodID JPy_Map_Entry_getValue_MID = NULL; +// java.util.Set +jclass JPy_Set_JClass = NULL; +jmethodID JPy_Set_Iterator_MID = NULL; +// java.util.Iterator +jclass JPy_Iterator_JClass = NULL; +jmethodID JPy_Iterator_next_MID = NULL; +jmethodID JPy_Iterator_hasNext_MID = NULL; + jclass JPy_RuntimeException_JClass = NULL; +jclass JPy_OutOfMemoryError_JClass = NULL; +jclass JPy_UnsupportedOperationException_JClass = NULL; +jclass JPy_FileNotFoundException_JClass = NULL; +jclass JPy_KeyError_JClass = NULL; +jclass JPy_StopIteration_JClass = NULL; // java.lang.Boolean jclass JPy_Boolean_JClass = NULL; @@ -198,11 +226,23 @@ jmethodID JPy_Number_DoubleValue_MID = NULL; jclass JPy_Void_JClass = NULL; jclass JPy_String_JClass = NULL; +jclass JPy_PyObject_JClass = NULL; +jclass JPy_PyDictWrapper_JClass = NULL; jmethodID JPy_PyObject_GetPointer_MID = NULL; jmethodID JPy_PyObject_Init_MID = NULL; jmethodID JPy_PyModule_Init_MID = NULL; +jmethodID JPy_PyDictWrapper_GetPointer_MID = NULL; + +// java.lang.Throwable +jclass JPy_Throwable_JClass = NULL; +jmethodID JPy_Throwable_getStackTrace_MID = NULL; +jmethodID JPy_Throwable_getCause_MID = NULL; + +// stack trace element +jclass JPy_StackTraceElement_JClass = NULL; + // }}} @@ -324,6 +364,12 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void) ///////////////////////////////////////////////////////////////////////// + JPy_Type_Translations = PyDict_New(); + Py_INCREF(JPy_Type_Translations); + PyModule_AddObject(JPy_Module, JPy_MODULE_ATTR_NAME_TYPE_TRANSLATIONS, JPy_Type_Translations); + + ///////////////////////////////////////////////////////////////////////// + if (PyType_Ready(&Diag_Type) < 0) { JPY_RETURN(NULL); } @@ -334,6 +380,15 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void) PyModule_AddObject(JPy_Module, "diag", pyDiag); } + if (PyType_Ready(&VerboseExceptions_Type) < 0) { + JPY_RETURN(NULL); + } + { + PyObject* pyVerboseExceptions = VerboseExceptions_New(); + Py_INCREF(pyVerboseExceptions); + PyModule_AddObject(JPy_Module, "VerboseExceptions", pyVerboseExceptions); + } + ///////////////////////////////////////////////////////////////////////// if (JPy_JVM != NULL) { @@ -582,12 +637,12 @@ PyObject* JPy_array(PyObject* self, PyObject* args) if (arrayRef == NULL) { return PyErr_NoMemory(); } - return (PyObject*) JObj_New(jenv, arrayRef); + return JObj_New(jenv, arrayRef); } else if (PySequence_Check(objInit)) { - if (JType_CreateJavaArray(jenv, componentType, objInit, &arrayRef) < 0) { + if (JType_CreateJavaArray(jenv, componentType, objInit, &arrayRef, JNI_FALSE) < 0) { return NULL; } - return (PyObject*) JObj_New(jenv, arrayRef); + return JObj_New(jenv, arrayRef); } else { PyErr_SetString(PyExc_ValueError, "array: argument 2 (init) must be either an integer array length or any sequence"); return NULL; @@ -692,14 +747,19 @@ jmethodID JPy_GetMethod(JNIEnv* jenv, jclass classRef, const char* name, const c int initGlobalPyObjectVars(JNIEnv* jenv) { + JPy_JType *dictType; + JPy_JType *keyErrorType; + JPy_JType *stopIterationType; + JPy_JPyObject = JType_GetTypeForName(jenv, "org.jpy.PyObject", JNI_FALSE); if (JPy_JPyObject == NULL) { // org.jpy.PyObject may not be on the classpath, which is ok PyErr_Clear(); return -1; } else { - DEFINE_METHOD(JPy_PyObject_GetPointer_MID, JPy_JPyObject->classRef, "getPointer", "()J"); - DEFINE_METHOD(JPy_PyObject_Init_MID, JPy_JPyObject->classRef, "", "(J)V"); + JPy_PyObject_JClass = JPy_JPyObject->classRef; + DEFINE_METHOD(JPy_PyObject_GetPointer_MID, JPy_PyObject_JClass, "getPointer", "()J"); + DEFINE_METHOD(JPy_PyObject_Init_MID, JPy_PyObject_JClass, "", "(J)V"); } JPy_JPyModule = JType_GetTypeForName(jenv, "org.jpy.PyModule", JNI_FALSE); @@ -708,6 +768,32 @@ int initGlobalPyObjectVars(JNIEnv* jenv) PyErr_Clear(); return -1; } + + dictType = JType_GetTypeForName(jenv, "org.jpy.PyDictWrapper", JNI_FALSE); + if (dictType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_PyDictWrapper_JClass = dictType->classRef; + DEFINE_METHOD(JPy_PyDictWrapper_GetPointer_MID, JPy_PyDictWrapper_JClass, "getPointer", "()J"); + } + + keyErrorType = JType_GetTypeForName(jenv, "org.jpy.KeyError", JNI_FALSE); + if (keyErrorType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_KeyError_JClass = keyErrorType->classRef; + } + + stopIterationType = JType_GetTypeForName(jenv, "org.jpy.StopIteration", JNI_FALSE); + if (stopIterationType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_StopIteration_JClass = stopIterationType->classRef; + } + return 0; } @@ -752,7 +838,28 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_METHOD(JPy_Method_GetParameterTypes_MID, JPy_Method_JClass, "getParameterTypes", "()[Ljava/lang/Class;"); DEFINE_METHOD(JPy_Method_GetReturnType_MID, JPy_Method_JClass, "getReturnType", "()Ljava/lang/Class;"); + DEFINE_CLASS(JPy_Map_JClass, "java/util/Map"); + DEFINE_METHOD(JPy_Map_entrySet_MID, JPy_Map_JClass, "entrySet", "()Ljava/util/Set;"); + DEFINE_METHOD(JPy_Map_put_MID, JPy_Map_JClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Map_clear_MID, JPy_Map_JClass, "clear", "()V"); + + DEFINE_CLASS(JPy_Map_Entry_JClass, "java/util/Map$Entry"); + DEFINE_METHOD(JPy_Map_Entry_getKey_MID, JPy_Map_Entry_JClass, "getKey", "()Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Map_Entry_getValue_MID, JPy_Map_Entry_JClass, "getValue", "()Ljava/lang/Object;"); + + + // java.util.Set + DEFINE_CLASS(JPy_Set_JClass, "java/util/Set"); + DEFINE_METHOD(JPy_Set_Iterator_MID, JPy_Set_JClass, "iterator", "()Ljava/util/Iterator;"); + // java.util.Iterator + DEFINE_CLASS(JPy_Iterator_JClass, "java/util/Iterator"); + DEFINE_METHOD(JPy_Iterator_next_MID, JPy_Iterator_JClass, "next", "()Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Iterator_hasNext_MID, JPy_Iterator_JClass, "hasNext", "()Z"); + DEFINE_CLASS(JPy_RuntimeException_JClass, "java/lang/RuntimeException"); + DEFINE_CLASS(JPy_OutOfMemoryError_JClass, "java/lang/OutOfMemoryError"); + DEFINE_CLASS(JPy_FileNotFoundException_JClass, "java/io/FileNotFoundException"); + DEFINE_CLASS(JPy_UnsupportedOperationException_JClass, "java/lang/UnsupportedOperationException"); DEFINE_CLASS(JPy_Boolean_JClass, "java/lang/Boolean"); DEFINE_METHOD(JPy_Boolean_Init_MID, JPy_Boolean_JClass, "", "(Z)V"); @@ -788,6 +895,8 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_CLASS(JPy_Void_JClass, "java/lang/Void"); DEFINE_CLASS(JPy_String_JClass, "java/lang/String"); + DEFINE_CLASS(JPy_Throwable_JClass, "java/lang/Throwable"); + DEFINE_CLASS(JPy_StackTraceElement_JClass, "java/lang/StackTraceElement"); // Non-Object types: Primitive types and void. DEFINE_NON_OBJECT_TYPE(JPy_JBoolean, JPy_Boolean_JClass); @@ -814,6 +923,10 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_OBJECT_TYPE(JPy_JDoubleObj, JPy_Double_JClass); // Other objects. DEFINE_OBJECT_TYPE(JPy_JString, JPy_String_JClass); + DEFINE_OBJECT_TYPE(JPy_JThrowable, JPy_Throwable_JClass); + DEFINE_OBJECT_TYPE(JPy_JStackTraceElement, JPy_StackTraceElement_JClass); + DEFINE_METHOD(JPy_Throwable_getCause_MID, JPy_Throwable_JClass, "getCause", "()Ljava/lang/Throwable;"); + DEFINE_METHOD(JPy_Throwable_getStackTrace_MID, JPy_Throwable_JClass, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); // JType_AddClassAttribute is actually called from within JType_GetType(), but not for // JPy_JObject and JPy_JClass for an obvious reason. So we do it now: @@ -947,31 +1060,189 @@ void JPy_ClearGlobalVars(JNIEnv* jenv) JPy_JPyModule = NULL; } +#define AT_STRING "\tat " +#define AT_STRLEN 4 +#define CAUSED_BY_STRING "caused by " +#define CAUSED_BY_STRLEN 10 +#define ELIDED_STRING_MAX_SIZE 30 void JPy_HandleJavaException(JNIEnv* jenv) { jthrowable error = (*jenv)->ExceptionOccurred(jenv); if (error != NULL) { jstring message; + int allocError = 0; if (JPy_DiagFlags != 0) { (*jenv)->ExceptionDescribe(jenv); } - message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID); - if (message != NULL) { - const char* messageChars; - - messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); - if (messageChars != NULL) { - PyErr_Format(PyExc_RuntimeError, "%s", messageChars); - (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + if (JPy_VerboseExceptions) { + char *stackTraceString; + size_t stackTraceLength = 0; + jthrowable cause = error; + jarray enclosingElements = NULL; + jint enclosingSize = 0; + + stackTraceString = strdup(""); + + do { + /* We want the type and the detail string, which is actually what a Throwable toString() does by + * default, as does the default printStackTrace(). */ + jint ii; + + jarray stackTrace; + jint stackTraceElements; + jint lastElementToPrint; + jint enclosingIndex; + + if (stackTraceLength > 0) { + char *newStackString; + + newStackString = realloc(stackTraceString, CAUSED_BY_STRLEN + 1 + stackTraceLength); + if (newStackString == NULL) { + allocError = 1; + break; + } + stackTraceString = newStackString; + strcat(stackTraceString, CAUSED_BY_STRING); + stackTraceLength += CAUSED_BY_STRLEN; + } + + message = (jstring) (*jenv)->CallObjectMethod(jenv, cause, JPy_Object_ToString_MID); + if (message != NULL) { + const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars != NULL) { + char *newStackString; + size_t len = strlen(messageChars); + + newStackString = realloc(stackTraceString, len + 2 + stackTraceLength); + if (newStackString == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + allocError = 1; + break; + } + + stackTraceString = newStackString; + strcat(stackTraceString, messageChars); + stackTraceString[stackTraceLength + len] = '\n'; + stackTraceString[stackTraceLength + len + 1] = '\0'; + stackTraceLength += (len + 1); + + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } else { + allocError = 1; + break; + } + (*jenv)->DeleteLocalRef(jenv, message); + } + + /* We should assemble a string based on the stack trace. */ + stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID); + stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace); + lastElementToPrint = stackTraceElements - 1; + enclosingIndex = enclosingSize - 1; + + while (lastElementToPrint >= 0 && enclosingIndex >= 0) { + jobject thisElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, lastElementToPrint); + jobject thatElement = (*jenv)->GetObjectArrayElement(jenv, enclosingElements, enclosingIndex); + + // if they are equal, let's decrement, otherwise we break + jboolean equal = (*jenv)->CallBooleanMethod(jenv, thisElement, JPy_Object_Equals_MID, thatElement); + if (!equal) { + break; + } + + lastElementToPrint--; + enclosingIndex--; + } + + for (ii = 0; ii <= lastElementToPrint; ++ii) { + jobject traceElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, ii); + if (traceElement != NULL) { + message = (jstring) (*jenv)->CallObjectMethod(jenv, traceElement, JPy_Object_ToString_MID); + if (message != NULL) { + size_t len; + char *newStackString; + const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars == NULL) { + allocError = 1; + break; + } + + len = strlen(messageChars); + + newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength); + if (newStackString == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + allocError = 1; + break; + } + + stackTraceString = newStackString; + strcat(stackTraceString, AT_STRING); + strcat(stackTraceString, messageChars); + stackTraceString[stackTraceLength + len + AT_STRLEN] = '\n'; + stackTraceString[stackTraceLength + len + AT_STRLEN + 1] = '\0'; + stackTraceLength += (len + 1 + AT_STRLEN); + + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } + + } + } + + if (lastElementToPrint < stackTraceElements - 1) { + int written; + char *newStackString = realloc(stackTraceString, stackTraceLength + ELIDED_STRING_MAX_SIZE); + if (newStackString == NULL) { + allocError = 1; + break; + } + + stackTraceString = newStackString; + stackTraceString[stackTraceLength + ELIDED_STRING_MAX_SIZE - 1] = '\0'; + + written = snprintf(stackTraceString + stackTraceLength, ELIDED_STRING_MAX_SIZE - 1, "\t... %d more\n", (stackTraceElements - lastElementToPrint) - 1); + if (written > (ELIDED_STRING_MAX_SIZE - 1)) { + stackTraceLength += (ELIDED_STRING_MAX_SIZE - 1); + } else { + stackTraceLength += written; + } + } + + /** So we can eliminate extra entries. */ + enclosingElements = stackTrace; + enclosingSize = stackTraceElements; + + /** Now the next cause. */ + cause = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getCause_MID); + } while (cause != NULL && !allocError); + + if (allocError == 0 && stackTraceString != NULL) { + PyErr_Format(PyExc_RuntimeError, "%s", stackTraceString); } else { - PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, but failed to allocate message text"); + PyErr_SetString(PyExc_RuntimeError, + "Java VM exception occurred, but failed to allocate message text"); } - (*jenv)->DeleteLocalRef(jenv, message); + free(stackTraceString); } else { - PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message"); + message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID); + if (message != NULL) { + const char *messageChars; + + messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars != NULL) { + PyErr_Format(PyExc_RuntimeError, "%s", messageChars); + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } else { + PyErr_SetString(PyExc_RuntimeError, + "Java VM exception occurred, but failed to allocate message text"); + } + (*jenv)->DeleteLocalRef(jenv, message); + } else { + PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message"); + } } (*jenv)->DeleteLocalRef(jenv, error); @@ -987,6 +1258,7 @@ void JPy_free(void* unused) JPy_Module = NULL; JPy_Types = NULL; JPy_Type_Callbacks = NULL; + JPy_Type_Translations = NULL; JException_Type = NULL; JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "JPy_free: done freeing module data\n"); diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index 6ded2c8093..7070da2b6c 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ #ifndef JPY_MODULE_H @@ -32,6 +35,7 @@ extern "C" { extern PyObject* JPy_Module; extern PyObject* JPy_Types; extern PyObject* JPy_Type_Callbacks; +extern PyObject* JPy_Type_Translations; extern PyObject* JException_Type; extern JavaVM* JPy_JVM; @@ -42,6 +46,7 @@ extern jboolean JPy_MustDestroyJVM; #define JPy_MODULE_ATTR_NAME_TYPES "types" #define JPy_MODULE_ATTR_NAME_TYPE_CALLBACKS "type_callbacks" +#define JPy_MODULE_ATTR_NAME_TYPE_TRANSLATIONS "type_translations" /** @@ -148,8 +153,28 @@ extern jclass JPy_Field_JClass; extern jmethodID JPy_Field_GetName_MID; extern jmethodID JPy_Field_GetModifiers_MID; extern jmethodID JPy_Field_GetType_MID; +// java.util.Map +extern jclass JPy_Map_JClass; +extern jclass JPy_Map_Entry_JClass; +extern jmethodID JPy_Map_entrySet_MID; +extern jmethodID JPy_Map_put_MID; +extern jmethodID JPy_Map_clear_MID; +extern jmethodID JPy_Map_Entry_getKey_MID; +extern jmethodID JPy_Map_Entry_getValue_MID; +// java.util.Set +extern jclass JPy_Set_JClass; +extern jmethodID JPy_Set_Iterator_MID; +// java.util.Iterator +extern jclass JPy_Iterator_JClass; +extern jmethodID JPy_Iterator_next_MID; +extern jmethodID JPy_Iterator_hasNext_MID; extern jclass JPy_RuntimeException_JClass; +extern jclass JPy_OutOfMemoryError_JClass; +extern jclass JPy_FileNotFoundException_JClass; +extern jclass JPy_UnsupportedOperationException_JClass; +extern jclass JPy_KeyError_JClass; +extern jclass JPy_StopIteration_JClass; extern jclass JPy_Boolean_JClass; extern jmethodID JPy_Boolean_Init_MID; @@ -185,9 +210,13 @@ extern jmethodID JPy_Number_DoubleValue_MID; extern jclass JPy_String_JClass; extern jclass JPy_Void_JClass; +extern jclass JPy_PyObject_JClass; extern jmethodID JPy_PyObject_GetPointer_MID; extern jmethodID JPy_PyObject_Init_MID; +extern jclass JPy_PyDictWrapper_JClass; +extern jmethodID JPy_PyDictWrapper_GetPointer_MID; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/main/c/jpy_verboseexcept.c b/src/main/c/jpy_verboseexcept.c new file mode 100644 index 0000000000..3c96fcf327 --- /dev/null +++ b/src/main/c/jpy_verboseexcept.c @@ -0,0 +1,97 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ + +#include +#include "jpy_verboseexcept.h" + +int JPy_VerboseExceptions = 0; + +PyObject* VerboseExceptions_New(void) +{ + return PyObject_New(PyObject, &VerboseExceptions_Type); +} + + +PyObject* VerboseExceptions_getattro(PyObject* self, PyObject *attr_name) +{ + if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) { + return PyBool_FromLong(JPy_VerboseExceptions); + } else { + return PyObject_GenericGetAttr(self, attr_name); + } +} + + +int VerboseExceptions_setattro(PyObject* self, PyObject *attr_name, PyObject *v) +{ + if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) { + if (PyBool_Check(v)) { + JPy_VerboseExceptions = v == Py_True; + } else { + PyErr_SetString(PyExc_ValueError, "value for 'flags' must be a boolean"); + return -1; + } + return 0; + } else { + return PyObject_GenericSetAttr(self, attr_name, v); + } +} + + +PyTypeObject VerboseExceptions_Type = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "jpy.VerboseExceptions", /* tp_name */ + sizeof (VerboseExceptions_Type), /* tp_basicsize */ + 0, /* tp_itemsize */ + NULL, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_reserved */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + NULL, /* tp_str */ + (getattrofunc) VerboseExceptions_getattro, /* tp_getattro */ + (setattrofunc) VerboseExceptions_setattro, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Controls python exception verbosity", /* tp_doc */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + NULL, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc) NULL, /* tp_init */ + NULL, /* tp_alloc */ + NULL, /* tp_new */ +}; diff --git a/src/main/c/jpy_verboseexcept.h b/src/main/c/jpy_verboseexcept.h new file mode 100644 index 0000000000..990972127b --- /dev/null +++ b/src/main/c/jpy_verboseexcept.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ + +#ifndef JPY_VERBOSEEXCEPT_H +#define JPY_VERBOSEEXCEPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "jpy_compat.h" + +extern PyTypeObject VerboseExceptions_Type; +extern int JPy_VerboseExceptions; + +PyObject* VerboseExceptions_New(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !JPY_DIAG_H */ \ No newline at end of file diff --git a/src/main/java/org/jpy/KeyError.java b/src/main/java/org/jpy/KeyError.java new file mode 100644 index 0000000000..c3a44c5604 --- /dev/null +++ b/src/main/java/org/jpy/KeyError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +/** + * Translation of Python KeyErrors so that they can be programmatically detected from Java. + */ +public class KeyError extends RuntimeException { + KeyError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java new file mode 100644 index 0000000000..7bf97d0406 --- /dev/null +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -0,0 +1,258 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +import java.util.*; + +/** + * A simple wrapper around PyObjects that are actually Python dictionaries, to present the most useful parts of a + * Map interface. + */ +public class PyDictWrapper implements Map { + private PyObject pyObject; + + PyDictWrapper(PyObject pyObject) { + this.pyObject = pyObject; + } + + @Override + public int size() { + return pyObject.callMethod("__len__").getIntValue(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return pyObject.callMethod("has_key", key).getBooleanValue(); + } + + /** + * An extension to the Map interface that allows the use of String keys without generating warnings. + */ + public boolean containsKey(String key) { + return containsKey((Object)key); + } + + @Override + public boolean containsValue(Object value) { + return pyObject.callMethod("values").asList().contains(value); + } + + @Override + public PyObject get(Object key) { + return pyObject.callMethod("__getitem__", key); + } + + /** + * An extension to the Map interface that allows the use of String keys without generating warnings. + */ + public PyObject get(String key) { + return pyObject.callMethod("__getitem__", key); + } + + @Override + public PyObject put(PyObject key, PyObject value) { + return putObject(key, value); + } + + /** + * An extension to the Map interface that allows the use of Object key-values without generating warnings. + */ + public PyObject putObject(Object key, Object value) { + return pyObject.callMethod("__setitem__", key, value); + } + + @Override + public PyObject remove(Object key) { + try { + PyObject value = get(key); + pyObject.callMethod("__delitem__", key); + return value; + } catch (KeyError ke) { + return null; + } + } + + public PyObject remove(String key) { + return remove((Object)key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + pyObject.callMethod("clear"); + } + + @Override + public Set keySet() { + return new LinkedHashSet<>(pyObject.callMethod("keys").asList()); + } + + @Override + public Collection values() { + return pyObject.callMethod("values").asList(); + } + + @Override + public Set> entrySet() { + return new EntrySet(); + } + + /** + * Gets the underlying PyObject. + * + * @return the PyObject wrapped by this dictionary. + */ + public PyObject unwrap() { + return pyObject; + } + + /** + * Gets the underlying pointer for this object. + * + * @return the pointer to the underlying Python object wrapped by this dictionary. + */ + long getPointer() { + return pyObject.getPointer(); + } + + /** + * Copy this dictionary into a new dictionary. + * + * @return a wrapped copy of this Python dictionary. + */ + public PyDictWrapper copy() { + return new PyDictWrapper(PyLib.copyDict(pyObject.getPointer())); + } + + private class EntrySet implements Set> { + @Override + public int size() { + return PyDictWrapper.this.size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> iterator() { + return new Iterator>() { + PyModule builtins = PyModule.getBuiltins(); + PyObject it = pyObject.callMethod("__iter__"); + PyObject next = prepareNext(); + + private PyObject prepareNext() { + try { + return next = builtins.call("next", it); + } catch (StopIteration e) { + return next = null; + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Entry next() { + final PyObject oldNext = next; + prepareNext(); + return new Entry() { + + @Override + public PyObject getKey() { + return oldNext; + } + + @Override + public PyObject getValue() { + return get(oldNext); + } + + @Override + public PyObject setValue(PyObject value) { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(Entry pyObjectPyObjectEntry) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index a34c00f6b1..4b3e3ffada 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -12,13 +12,16 @@ * 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. + * + * This file was modified by Illumon. + * */ package org.jpy; import java.io.File; +import java.io.FileNotFoundException; import java.util.ArrayList; -import java.util.Map; import static org.jpy.PyLibConfig.JPY_LIB_KEY; import static org.jpy.PyLibConfig.OS; @@ -200,11 +203,25 @@ public static void startPython(String... extraPaths) { } Diag.setFlags(Diag.F_EXEC); } + + String pythonHome = getProperty(PyLibConfig.JPY_CONFIG_PREFIX, false); + if (pythonHome != null) { + setPythonHome(pythonHome); + } startPython0(extraPaths); } static native boolean startPython0(String... paths); + + /** + * Does the equivalent of setting the PYTHONHOME environment variable. If used, + * this must be called prior to calling {@code startPython()}. + * Supported for Python 2.7, and Python 3.5 or higher + * @param pythonHome Path to Python Home (must be less than 256 characters!) + * @return true if successful, false if it fails + */ + public static native boolean setPythonHome(String pythonHome); /** * @return The Python interpreter version string. @@ -232,7 +249,14 @@ public static void stopPython() { @Deprecated public static native int execScript(String script); - static native long executeCode(String code, int start, Map globals, Map locals); + static native long executeCode(String code, int start, Object globals, Object locals); + + static native long executeScript + (String file, int start, Object globals, Object locals) throws FileNotFoundException; + + public static native PyObject getMainGlobals(); + + static native PyObject copyDict(long pyPointer); static native void incRef(long pointer); @@ -240,12 +264,33 @@ public static void stopPython() { static native int getIntValue(long pointer); + static native boolean getBooleanValue(long pointer); + static native double getDoubleValue(long pointer); static native String getStringValue(long pointer); static native Object getObjectValue(long pointer); + static native boolean isConvertible(long pointer); + static native boolean pyNoneCheck(long pointer); + static native boolean pyDictCheck(long pointer); + static native boolean pyListCheck(long pointer); + static native boolean pyBoolCheck(long pointer); + static native boolean pyIntCheck(long pointer); + static native boolean pyLongCheck(long pointer); + static native boolean pyFloatCheck(long pointer); + static native boolean pyStringCheck(long pointer); + static native boolean pyCallableCheck(long pointer); + + static native long getType(long pointer); + + static native String str(long pointer); + + static native String repr(long pointer); + + static native PyObject newDict(); + static native T[] getObjectArrayValue(long pointer, Class itemType); static native long importModule(String name); @@ -284,6 +329,25 @@ public static void stopPython() { */ static native void setAttributeValue(long pointer, String name, T value, Class valueType); + /** + * Deletes the Python attribute given by {@code name} of the Python object pointed to by {@code pointer}. + *

+ * + * @param pointer Identifies the Python object which contains the attribute {@code name}. + * @param name The attribute name. + */ + static native void delAttribute(long pointer, String name); + + /** + * Checks for the existence the Python attribute given by {@code name} of the Python object pointed to by {@code pointer}. + *

+ * + * @param pointer Identifies the Python object which contains the attribute {@code name}. + * @param name The attribute name. + * @return true if the Python object has an attribute named {@code name} + */ + static native boolean hasAttribute(long pointer, String name); + /** * Calls a Python callable and returns the resulting Python object. *

diff --git a/src/main/java/org/jpy/PyLibConfig.java b/src/main/java/org/jpy/PyLibConfig.java index 3e16e62047..4a82342cec 100644 --- a/src/main/java/org/jpy/PyLibConfig.java +++ b/src/main/java/org/jpy/PyLibConfig.java @@ -38,6 +38,7 @@ class PyLibConfig { public static final String JPY_LIB_KEY = "jpy.jpyLib"; public static final String JPY_CONFIG_KEY = "jpy.config"; public static final String JPY_CONFIG_RESOURCE = "jpyconfig.properties"; + public static final String JPY_CONFIG_PREFIX = "jpy.pythonPrefix"; public enum OS { WINDOWS, diff --git a/src/main/java/org/jpy/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java new file mode 100644 index 0000000000..29068f7918 --- /dev/null +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -0,0 +1,213 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +import java.util.*; + +/** + * A simple wrapper around a Python List object that implements a java List of PyObjects. + */ +class PyListWrapper implements List { + private PyObject pyObject; + + PyListWrapper(PyObject pyObject) { + this.pyObject = pyObject; + } + + @Override + public int size() { + return pyObject.callMethod("__len__").getIntValue(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + for (PyObject obj : this) { + if (obj.equals(o)) { + return true; + } + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int ii = 0; + int size = size(); + + @Override + public boolean hasNext() { + return ii < size; + } + + @Override + public PyObject next() { + return get(ii++); + } + }; + } + + @Override + public PyObject[] toArray() { + int size = size(); + + PyObject [] result = new PyObject[size]; + for (int ii = 0; ii < size; ++ii) { + result[ii] = get(ii); + } + + return result; + } + + @Override + public T[] toArray(T[] a) { + int size = size(); + + if (a.length < size) { + a = Arrays.copyOf(a, size); + } + for (int ii = 0; ii < size; ++ii) { + //noinspection unchecked + a[ii] = (T)get(ii); + } + if (a.length > size) { + a[size] = null; + } + + return a; + } + + @Override + public boolean add(PyObject pyObject) { + pyObject.callMethod("append", pyObject); + return true; + } + + @Override + public boolean remove(Object o) { + try { + pyObject.callMethod("remove", pyObject); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean containsAll(Collection c) { + return c.stream().allMatch(this::contains); + } + + @Override + public boolean addAll(Collection c) { + boolean result = false; + for (PyObject po : c) { + result |= add(po); + } + return result; + } + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + pyObject.callMethod("clear"); + } + + @Override + public PyObject get(int index) { + return pyObject.callMethod("__getitem__", index); + } + + @Override + public PyObject set(int index, PyObject element) { + return pyObject.callMethod("__setitem__", index, element); + } + + @Override + public void add(int index, PyObject element) { + pyObject.callMethod("insert", index, element); + } + + @Override + public PyObject remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + int size = size(); + + for (int ii = 0; ii < size; ++ii) { + PyObject pyObject = get(ii); + if (pyObject == null ? o == null : pyObject.equals(o)) { + return ii; + } + } + + return -1; + } + + @Override + public int lastIndexOf(Object o) { + int size = size(); + + for (int ii = size - 1; ii >= 0; --ii) { + PyObject pyObject = get(ii); + if (pyObject == null ? o == null : pyObject.equals(o)) { + return ii; + } + } + + return -1; + } + + @Override + public ListIterator listIterator() { + throw new UnsupportedOperationException(); + } + + @Override + public ListIterator listIterator(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/jpy/PyModule.java b/src/main/java/org/jpy/PyModule.java index 9794cc7ae4..182a3b759a 100644 --- a/src/main/java/org/jpy/PyModule.java +++ b/src/main/java/org/jpy/PyModule.java @@ -16,6 +16,7 @@ package org.jpy; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; /** @@ -89,6 +90,7 @@ public static PyModule getBuiltins() { */ public static PyModule importModule(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.importModule(name); return pointer != 0 ? new PyModule(name, pointer) : null; } @@ -102,6 +104,7 @@ public static PyModule importModule(String name) { * @since 0.8 */ public static PyObject extendSysPath(String modulePath, boolean prepend) { + Objects.requireNonNull(modulePath, "path must not be null"); PyModule sys = importModule("sys"); PyObject sysPath = sys.getAttribute("path"); if (prepend) { @@ -121,8 +124,10 @@ public static PyObject extendSysPath(String modulePath, boolean prepend) { * @param The interface name. * @return A (proxy) instance implementing the given interface. */ + @Override public T createProxy(Class type) { assertPythonRuns(); + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.FUNCTION, type); } } diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index a50f52cacb..36212fa0d5 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -12,13 +12,18 @@ * 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. + * + * This file was modified by Illumon. + * */ package org.jpy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; -import java.util.Map; +import java.util.List; +import java.io.FileNotFoundException; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; @@ -54,6 +59,17 @@ public static PyObject executeCode(String code, PyInputMode mode) { return executeCode(code, mode, null, null); } + /** + * Executes Python source script. + * + * @param script The Python source script. + * @param mode The execution mode. + * @return The result of executing the script as a Python object. + */ + public static PyObject executeScript(String script, PyInputMode mode) throws FileNotFoundException { + return executeScript(script, mode, null, null); + } + /** * Executes Python source code in the context specified by the {@code globals} and {@code locals} maps. *

@@ -62,20 +78,36 @@ public static PyObject executeCode(String code, PyInputMode mode) { * * @param code The Python source code. * @param mode The execution mode. - * @param globals The global variables to be set. - * @param locals The locals variables to be set. + * @param globals The global variables to be set, or {@code null}. + * @param locals The locals variables to be set, or {@code null}. * @return The result of executing the code as a Python object. */ - public static PyObject executeCode(String code, PyInputMode mode, Map globals, Map locals) { - if (code == null) { - throw new NullPointerException("code must not be null"); - } - if (mode == null) { - throw new NullPointerException("mode must not be null"); - } + public static PyObject executeCode(String code, PyInputMode mode, Object globals, Object locals) { + Objects.requireNonNull(code, "code must not be null"); + Objects.requireNonNull(mode, "mode must not be null"); return new PyObject(PyLib.executeCode(code, mode.value(), globals, locals)); } + + /** + * Executes Python source script in the context specified by the {@code globals} and {@code locals} maps. + *

+ * If a Java value in the {@code globals} and {@code locals} maps cannot be directly converted into a Python object, a Java wrapper will be created instead. + * If a Java value is a wrapped Python object of type {@link PyObject}, it will be unwrapped. + * + * @param script The Python source script. + * @param mode The execution mode. + * @param globals The global variables to be set, or {@code null}. + * @param locals The locals variables to be set, or {@code null}. + * @return The result of executing the script as a Python object. + * @throws FileNotFoundException if the script file is not found + */ + public static PyObject executeScript(String script, PyInputMode mode, Object globals, Object locals) throws FileNotFoundException { + Objects.requireNonNull(script, "script must not be null"); + Objects.requireNonNull(mode, "mode must not be null"); + return new PyObject(PyLib.executeScript(script, mode.value(), globals, locals)); + } + /** * Decrements the reference count of the Python object which this class represents. * @@ -106,6 +138,14 @@ public int getIntValue() { return PyLib.getIntValue(getPointer()); } + /** + * @return This Python object as a Java {@code boolean} value. + */ + public boolean getBooleanValue() { + assertPythonRuns(); + return PyLib.getBooleanValue(getPointer()); + } + /** * @return This Python object as a Java {@code double} value. */ @@ -135,6 +175,80 @@ public Object getObjectValue() { return PyLib.getObjectValue(getPointer()); } + /** + * Gets the Python type object for this wrapped object. + * + * @return This Python object's type as a {@code PyObject} wrapped value. + */ + public PyObject getType() { + assertPythonRuns(); + return new PyObject(PyLib.getType(getPointer())); + } + + public boolean isDict() { + assertPythonRuns(); + return PyLib.pyDictCheck(getPointer()); + } + + public boolean isList() { + assertPythonRuns(); + return PyLib.pyListCheck(getPointer()); + } + + public boolean isBoolean() { + assertPythonRuns(); + return PyLib.pyBoolCheck(getPointer()); + } + + public boolean isLong() { + assertPythonRuns(); + return PyLib.pyLongCheck(getPointer()); + } + + public boolean isInt() { + assertPythonRuns(); + return PyLib.pyIntCheck(getPointer()); + } + + public boolean isNone() { + assertPythonRuns(); + return PyLib.pyNoneCheck(getPointer()); + } + + public boolean isFloat() { + assertPythonRuns(); + return PyLib.pyFloatCheck(getPointer()); + } + + public boolean isCallable() { + assertPythonRuns(); + return PyLib.pyCallableCheck(getPointer()); + } + + public boolean isString() { + assertPythonRuns(); + return PyLib.pyStringCheck(getPointer()); + } + + public boolean isConvertible() { + assertPythonRuns(); + return PyLib.isConvertible(getPointer()); + } + + public List asList() { + if (!isList()) { + throw new ClassCastException("Can not convert non-list type to a list!"); + } + return new PyListWrapper(this); + } + + public PyDictWrapper asDict() { + if (!isDict()) { + throw new ClassCastException("Can not convert non-list type to a dictionary!"); + } + return new PyDictWrapper(this); + } + /** * Gets this Python object as Java {@code Object[]} value of the given item type. * Appropriate type conversions from Python to Java will be performed as needed. @@ -149,6 +263,7 @@ public Object getObjectValue() { */ public T[] getObjectArrayValue(Class itemType) { assertPythonRuns(); + Objects.requireNonNull(itemType, "itemType must not be null"); return PyLib.getObjectArrayValue(getPointer(), itemType); } @@ -163,6 +278,7 @@ public T[] getObjectArrayValue(Class itemType) { */ public PyObject getAttribute(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.getAttributeObject(getPointer(), name); return pointer != 0 ? new PyObject(pointer) : null; } @@ -175,12 +291,13 @@ public PyObject getAttribute(String name) { * If the Python value is a wrapped Java object, it will be unwrapped. * * @param name A name of the Python attribute. - * @param valueType The type of the value or {@code null}, if unknown + * @param valueType The type of the value or {@code null}, if unknown. * @param The expected value type name. * @return The Python attribute value as Java object. */ public T getAttribute(String name, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); return PyLib.getAttributeValue(getPointer(), name, valueType); } @@ -191,14 +308,38 @@ public T getAttribute(String name, Class valueType) { * If the Java {@code value} is a wrapped Python object of type {@link PyObject}, it will be unwrapped. * * @param name A name of the Python attribute. - * @param value The new attribute value as Java object. + * @param value The new attribute value as Java object. May be {@code null}. * @param The value type name. */ public void setAttribute(String name, T value) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, value != null ? value.getClass() : null); } + /** + * Deletes the value of a Python attribute. + * + * @param name the name of the Python attribute. + */ + public void delAttribute(String name) { + assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); + PyLib.delAttribute(getPointer(), name); + } + + /** + * Checks for the existence of a Python attribute.. + * + * @param name the name of the Python attribute. + * @return whether this attribute exists for this object + */ + public boolean hasAttribute(String name) { + assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); + return PyLib.hasAttribute(getPointer(), name); + } + /** * Sets the value of a Python attribute from the given Java object and Java type (if given). * Appropriate type conversions from Java to Python will be performed using the given value type. @@ -213,6 +354,7 @@ public void setAttribute(String name, T value) { */ public void setAttribute(String name, T value, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, valueType); } @@ -228,6 +370,7 @@ public void setAttribute(String name, T value, Class valueType) */ public PyObject callMethod(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), true, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -244,6 +387,7 @@ public PyObject callMethod(String name, Object... args) { */ public PyObject call(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), false, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -259,6 +403,7 @@ public PyObject call(String name, Object... args) { public T createProxy(Class type) { assertPythonRuns(); //noinspection unchecked + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.METHOD, type); } @@ -278,14 +423,24 @@ public Object createProxy(PyLib.CallableKind callableKind, Class... types) { } /** - * Gets a string representation of the object using the format "PyObject(pointer=<value>)". + * Gets the python string representation of this object. * * @return A string representation of the object. * @see #getPointer() */ @Override public final String toString() { - return String.format("%s(pointer=0x%s)", getClass().getSimpleName(), Long.toHexString(pointer)); + return PyLib.str(pointer); + } + + /** + * Gets a the python repr of this object + * + * @return A string representation of the object. + * @see #getPointer() + */ + public final String repr() { + return PyLib.repr(pointer); } /** diff --git a/src/main/java/org/jpy/PyProxyHandler.java b/src/main/java/org/jpy/PyProxyHandler.java index 862abb49dc..37ee54579e 100644 --- a/src/main/java/org/jpy/PyProxyHandler.java +++ b/src/main/java/org/jpy/PyProxyHandler.java @@ -1,22 +1,23 @@ /* * Copyright 2015 Brockmann Consult GmbH * - * 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 + * 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 + * 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. + * 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. */ package org.jpy; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; import java.lang.reflect.Method; import java.util.Arrays; @@ -24,15 +25,33 @@ /** * The {@code InvocationHandler} for used by the proxy instances created by the - * {@link PyObject#createProxy(Class)} and {@link PyModule#createProxy(Class)} methods. + * {@link PyObject#createProxy(Class)} and {@link PyModule#createProxy(Class)} + * methods. * * @author Norman Fomferra * @since 0.7 */ class PyProxyHandler implements InvocationHandler { + // preloaded Method objects for the methods in java.lang.Object + private static Method hashCodeMethod; + + private static Method equalsMethod; + + private static Method toStringMethod; + static { + try { + hashCodeMethod = Object.class.getMethod("hashCode"); + equalsMethod = Object.class.getMethod("equals", new Class[] { Object.class }); + toStringMethod = Object.class.getMethod("toString"); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + private final PyObject pyObject; + private final PyLib.CallableKind callableKind; - + public PyProxyHandler(PyObject pyObject, PyLib.CallableKind callableKind) { if (pyObject == null) { throw new NullPointerException("pyObject"); @@ -40,25 +59,94 @@ public PyProxyHandler(PyObject pyObject, PyLib.CallableKind callableKind) { this.pyObject = pyObject; this.callableKind = callableKind; } - + @Override public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable { assertPythonRuns(); - + if ((PyLib.Diag.getFlags() & PyLib.Diag.F_METH) != 0) { - System.out.printf("org.jpy.PyProxyHandler: invoke: %s(%s) on pyObject=%s in thread %s\n", - method.getName(), - Arrays.toString(args), - Long.toHexString(this.pyObject.getPointer()), - Thread.currentThread()); + System.out.printf("org.jpy.PyProxyHandler: invoke: %s(%s) on pyObject=%s in thread %s\n", method.getName(), + Arrays.toString(args), Long.toHexString(this.pyObject.getPointer()), Thread.currentThread()); } - - return PyLib.callAndReturnValue(this.pyObject.getPointer(), - callableKind == PyLib.CallableKind.METHOD, - method.getName(), - args != null ? args.length : 0, - args, - method.getParameterTypes(), - method.getReturnType()); + String methodName = method.getName(); + Class returnType = method.getReturnType(); + if (method.equals(hashCodeMethod)) { + return callPythonHash(); + } else if (method.equals(equalsMethod)) { + if (isProxyEqualsEligible(proxyObject, args[0])) { + PyObject otherPyObject = proxyGetOtherPyObject(proxyObject, args[0]); + if (this.pyObject == otherPyObject) { + return true; + } else { + args[0] = otherPyObject; + if (this.pyObject.hasAttribute("__eq__")) { + PyObject eqMethPtr = this.pyObject.getAttribute("__eq__"); + if (!eqMethPtr.hasAttribute("__func__")) { // Must not + // be + // implemented + return false; + } + } else { + return false; + } + } + } else { + return false; + } + // It's proxy eligible, but not same object, and __eq__ was + // implemented + // so defer to the Python __eq__ + methodName = "__eq__"; + } else if (method.equals(toStringMethod)) { + methodName = "__str__"; + } + + return PyLib.callAndReturnValue(this.pyObject.getPointer(), callableKind == PyLib.CallableKind.METHOD, + methodName, args != null ? args.length : 0, args, method.getParameterTypes(), returnType); + } + + /** + * Determines if the two proxy objects implement the same interfaces + * + * @param proxyObject + * @param otherObject + * @return + */ + private boolean isProxyEqualsEligible(Object proxyObject, Object otherObject) { + boolean result = ((proxyObject.getClass() == otherObject.getClass()) + && (Arrays.deepEquals(proxyObject.getClass().getInterfaces(), otherObject.getClass().getInterfaces()))); + + return result; + } + + /** + * Determines the corresponding Python object for the other object passed + * + * @param proxyObject + * @param otherObject + * @return + */ + private PyObject proxyGetOtherPyObject(Object proxyObject, Object otherObject) { + PyObject result = null; + InvocationHandler otherProxyHandler = Proxy.getInvocationHandler(otherObject); + if (otherProxyHandler.getClass() == this.getClass()) { + PyProxyHandler otherPyProxyHandler = (PyProxyHandler) otherProxyHandler; + result = otherPyProxyHandler.pyObject; + } + + return result; + } + + /** + * Calls the Python __hash__ function on the Python object, and returns the + * last 32 bits of it, since Python hash codes are 64 bits on 64 bit + * machines. + * + * @return + */ + private int callPythonHash() { + long pythonHash = PyLib.callAndReturnValue(this.pyObject.getPointer(), true, "__hash__", 0, null, + new Class[0], Long.class); + return (int) pythonHash; } } diff --git a/src/main/java/org/jpy/StopIteration.java b/src/main/java/org/jpy/StopIteration.java new file mode 100644 index 0000000000..1e5451b95f --- /dev/null +++ b/src/main/java/org/jpy/StopIteration.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +/** + * Translation of Python StopIteration so that they can be programmatically detected from Java. + */ +public class StopIteration extends RuntimeException { + StopIteration(String message) { + super(message); + } +} diff --git a/src/test/java/org/jpy/PyLibTest.java b/src/test/java/org/jpy/PyLibTest.java index 629e9c4f17..3144e36d20 100644 --- a/src/test/java/org/jpy/PyLibTest.java +++ b/src/test/java/org/jpy/PyLibTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.*; +import java.util.Map; + public class PyLibTest { @Before @@ -141,4 +143,37 @@ public void testCallAndReturnObject() throws Exception { //PyLib.Diag.setFlags(PyLib.Diag.F_ALL); assertEquals("Z", new PyObject(pointer).getStringValue()); } + + @Test + public void testGetMainGlobals() throws Exception { + PyObject globals = PyLib.getMainGlobals(); + Map dict = globals.asDict(); + assertFalse(dict.isEmpty()); + + boolean foundName = false; + + for (Map.Entry entry : dict.entrySet()) { + if (entry.getKey().isString()) { + if (entry.getKey().getObjectValue().equals("__name__")) { + foundName = true; + break; + } + } + } + + assertTrue(foundName); + } + + @Test + public void testNewDict() throws Exception { + PyObject dict = PyLib.newDict(); + assertTrue(dict.asDict().isEmpty()); + + PyObject globals = PyLib.getMainGlobals(); + + PyObject.executeCode("x = 42", PyInputMode.STATEMENT, globals, dict); + + assertFalse(dict.asDict().isEmpty()); + } + } diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index b3e3fd880c..51f402e9e2 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -1,17 +1,20 @@ /* * Copyright 2015 Brockmann Consult GmbH * - * 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 + * 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 + * 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. + * + * This file was modified by Illumon. + * */ package org.jpy; @@ -23,6 +26,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.Map; import java.util.List; import java.util.concurrent.*; @@ -32,43 +36,44 @@ * @author Norman Fomferra */ public class PyObjectTest { - + @Before public void setUp() throws Exception { - //System.out.println("PyModuleTest: Current thread: " + Thread.currentThread()); + // System.out.println("PyModuleTest: Current thread: " + + // Thread.currentThread()); String importPath = new File("src/test/python/fixtures").getCanonicalPath(); - + PyLib.startPython(importPath); assertEquals(true, PyLib.isPythonRunning()); - + PyLib.Diag.setFlags(PyLib.Diag.F_ALL); } - + @After public void tearDown() throws Exception { PyLib.Diag.setFlags(PyLib.Diag.F_OFF); PyLib.stopPython(); } - + @Test(expected = IllegalArgumentException.class) public void testNullPointer() throws Exception { new PyObject(0); } - + @Test public void testPointer() throws Exception { long pointer = PyLib.importModule("sys"); PyObject pyObject = new PyObject(pointer); assertEquals(pointer, pyObject.getPointer()); } - + @Test public void testToString() throws Exception { long pointer = PyLib.importModule("sys"); PyObject pyObject = new PyObject(pointer); - assertEquals("PyObject(pointer=0x" + Long.toHexString(pointer) + ")", pyObject.toString()); + assertEquals("", pyObject.toString()); } - + @Test public void testEqualsAndHashCode() throws Exception { long pointer1 = PyLib.importModule("sys"); @@ -86,60 +91,79 @@ public void testEqualsAndHashCode() throws Exception { assertEquals(pyObject1.hashCode(), new PyObject(pointer1).hashCode()); assertTrue(pyObject1.hashCode() != pyObject2.hashCode()); } - + @Test public void testExecuteCode_Stmt() throws Exception { PyObject pyObject = PyObject.executeCode("pass", PyInputMode.STATEMENT); assertNotNull(pyObject); assertNull(pyObject.getObjectValue()); } - + @Test public void testExecuteCode_IntExpr() throws Exception { PyObject pyObject = PyObject.executeCode("7465", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals(7465, pyObject.getIntValue()); } - + @Test public void testExecuteCode_DoubleExpr() throws Exception { PyObject pyObject = PyObject.executeCode("3.14", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals(3.14, pyObject.getDoubleValue(), 1e-10); } - + @Test public void testExecuteCode_StringExpr() throws Exception { PyObject pyObject = PyObject.executeCode("'Hello from Python'", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals("Hello from Python", pyObject.getStringValue()); } - + @Test public void testExecuteCode_Script() throws Exception { HashMap localMap = new HashMap<>(); - PyObject pyVoid = PyObject.executeCode("" + - "import jpy\n" + - "File = jpy.get_type('java.io.File')\n" + - "f = File('test.txt')", - PyInputMode.SCRIPT, - null, - localMap); + PyObject pyVoid = PyObject.executeCode( + "" + "import jpy\n" + "File = jpy.get_type('java.io.File')\n" + "f = File('test.txt')", + PyInputMode.SCRIPT, null, localMap); assertNotNull(pyVoid); assertEquals(null, pyVoid.getObjectValue()); - -/* assertNotNull(localMap.get("jpy")); assertNotNull(localMap.get("File")); assertNotNull(localMap.get("f")); assertEquals(PyObject.class, localMap.get("jpy").getClass()); assertEquals(Class.class, localMap.get("File").getClass()); assertEquals(File.class, localMap.get("f").getClass()); - + assertEquals(new File("test.txt"), localMap.get("f")); -*/ } + @Test + public void testLocals() throws Exception { + HashMap localMap = new HashMap<>(); + localMap.put("x", 7); + localMap.put("y", 6); + PyObject pyVoid = PyObject.executeCode("z = x + y", + PyInputMode.STATEMENT, + null, + localMap); + assertEquals(null, pyVoid.getObjectValue()); + + System.out.println("LocalMap size = " + localMap.size()); + for (Map.Entry entry : localMap.entrySet()) { + System.out.println("LocalMap[" + entry.getKey() + "]: " + entry.getValue()); + } + + assertNotNull(localMap.get("x")); + assertNotNull(localMap.get("y")); + assertNotNull(localMap.get("z")); + + assertEquals(7, localMap.get("x")); + assertEquals(6, localMap.get("y")); + assertEquals(13, localMap.get("z")); + + } + @Test public void testExecuteScript_ErrorExpr() throws Exception { try { @@ -149,7 +173,7 @@ public void testExecuteScript_ErrorExpr() throws Exception { assertTrue(e.getMessage().contains("SyntaxError")); } } - + @Test public void testCall() throws Exception { // Python equivalent: @@ -160,16 +184,16 @@ public void testCall() throws Exception { // PyModule builtins; try { - //Python 3.3 + // Python 3.3 builtins = PyModule.importModule("builtins"); } catch (Exception e) { - //Python 2.7 + // Python 2.7 builtins = PyModule.importModule("__builtin__"); } PyObject value = builtins.call("max", "A", "Z"); Assert.assertEquals("Z", value.getStringValue()); } - + @Test public void testGetSetAttributes() throws Exception { // Python equivalent: @@ -189,32 +213,57 @@ public void testGetSetAttributes() throws Exception { Assert.assertEquals("Tut tut!", a.getStringValue()); } + private boolean hasKey(Map dict, String key) { + for (Map.Entry entry : dict.entrySet()) { + if (entry.getKey().isString()) { + if (entry.getKey().getObjectValue().equals(key)) { + return true; + } + } + } + return false; + } + + @Test + public void testDictCopy() throws Exception { + PyObject globals = PyLib.getMainGlobals(); + PyDictWrapper dict = globals.asDict(); + PyDictWrapper dictCopy = dict.copy(); + + PyObject.executeCode("x = 42", PyInputMode.STATEMENT, globals, dictCopy.unwrap()); + + boolean copyHasX = hasKey(dictCopy, "x"); + boolean origHasX = hasKey(dict, "x"); + + assertTrue(copyHasX); + assertFalse(origHasX); + } + @Test public void testCreateProxyAndCallSingleThreaded() throws Exception { - //addTestDirToPythonSysPath(); + // addTestDirToPythonSysPath(); PyModule procModule = PyModule.importModule("proc_class"); PyObject procObj = procModule.call("Processor"); testCallProxySingleThreaded(procObj); } - + // see https://github.com/bcdev/jpy/issues/26 @Test public void testCreateProxyAndCallMultiThreaded() throws Exception { - //addTestDirToPythonSysPath(); - //PyLib.Diag.setFlags(PyLib.Diag.F_ALL); + // addTestDirToPythonSysPath(); + // PyLib.Diag.setFlags(PyLib.Diag.F_ALL); PyModule procModule = PyModule.importModule("proc_class"); PyObject procObj = procModule.call("Processor"); PyLib.Diag.setFlags(PyLib.Diag.F_ALL); testCallProxyMultiThreaded(procObj); - //PyLib.Diag.setFlags(PyLib.Diag.F_OFF); + // PyLib.Diag.setFlags(PyLib.Diag.F_OFF); } - - + static void testCallProxySingleThreaded(PyObject procObject) { // Cast the Python object to a Java object of type 'Processor' Processor processor = procObject.createProxy(Processor.class); assertNotNull(processor); - + String result; result = processor.initialize(); assertEquals("initialize", result); @@ -226,42 +275,42 @@ static void testCallProxySingleThreaded(PyObject procObject) { assertEquals("computeTile-100,200", result); result = processor.computeTile(200, 200, new float[100 * 100]); assertEquals("computeTile-200,200", result); + processor.setVal(1234); + int val = processor.getVal(); + assertEquals(val, 1234); + assertEquals(true, processor.check1234()); result = processor.dispose(); assertEquals("dispose", result); } - + static void testCallProxyMultiThreaded(PyObject procObject) { testCallProxyMultiThreaded(procObject, Executors.newFixedThreadPool(4)); } - + private static void testCallProxyMultiThreaded(PyObject procObject, ExecutorService executorService) { // Cast the Python object to a Java object of type 'Processor' final Processor processor = procObject.createProxy(Processor.class); assertNotNull(processor); - + String result; result = processor.initialize(); assertEquals("initialize", result); - + List> futures; try { - futures = executorService.invokeAll(Arrays.asList(new ProcessorTask(processor, 100, 100), - new ProcessorTask(processor, 200, 100), - new ProcessorTask(processor, 100, 200), - new ProcessorTask(processor, 200, 200)), 10, TimeUnit.SECONDS); - + futures = executorService.invokeAll( + Arrays.asList(new ProcessorTask(processor, 100, 100), new ProcessorTask(processor, 200, 100), + new ProcessorTask(processor, 100, 200), new ProcessorTask(processor, 200, 200)), + 10, TimeUnit.SECONDS); + result = processor.dispose(); assertEquals("dispose", result); - - String[] results = new String[]{ - futures.get(0).get(), - futures.get(1).get(), - futures.get(2).get(), - futures.get(3).get(), - }; - + + String[] results = new String[] { futures.get(0).get(), futures.get(1).get(), futures.get(2).get(), + futures.get(3).get(), }; + Arrays.sort(results); - + result = results[0]; assertEquals("computeTile-100,100", result); result = results[1]; @@ -270,34 +319,64 @@ private static void testCallProxyMultiThreaded(PyObject procObject, ExecutorServ assertEquals("computeTile-200,100", result); result = results[3]; assertEquals("computeTile-200,200", result); - + } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); fail(e.getMessage()); } } - + static void addTestDirToPythonSysPath() throws IOException { // Add module dir to sys.path in order to import file 'proc_class.py' String importPath = new File("src/test/python/fixtures").getCanonicalPath(); - //System.out.println("importPath = " + importPath); + // System.out.println("importPath = " + importPath); PyLib.execScript(String.format("import sys; sys.path.append('%s')", importPath.replace("\\", "\\\\"))); } - + private static class ProcessorTask implements Callable { final Processor processor; int x; int y; - + public ProcessorTask(Processor processor, int x, int y) { this.processor = processor; this.x = x; this.y = y; } - + @Override public String call() throws Exception { return processor.computeTile(x, y, new float[100 * 100]); } } + + private static interface ISimple { + public int getValue(); + } + + private static ISimple newTestObj(PyModule pyModule, String pythonClass, int value) { + PyObject procObj = pyModule.call(pythonClass, value); + ISimple simple = procObj.createProxy(ISimple.class); + return simple; + } + + @Test + public void testHashEqStr() { + PyModule pyModule = PyModule.importModule("hasheqstr"); + testOneClass(pyModule, "Simple", false); + testOneClass(pyModule, "HashSimple", true); + } + + private static void testOneClass(PyModule pyModule, String pythonClass, boolean eqResExpected) { + ISimple simple = newTestObj(pyModule, pythonClass, 1234); + int value = simple.getValue(); + assertEquals(value, 1234); + String rep = simple.toString(); + assertEquals(rep, pythonClass + ": 1234"); + ISimple simple2 = newTestObj(pyModule, pythonClass, 1234); + boolean eqRes = simple.equals(simple2); + assertEquals(eqRes, eqResExpected); + assertEquals(simple.hashCode() == simple2.hashCode(), eqResExpected); + assertEquals(simple.equals(simple), true); + } } diff --git a/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java b/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java new file mode 100644 index 0000000000..f2a3971266 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Charles P. Wright + */ +@SuppressWarnings("UnusedDeclaration") +public class CovariantOverloadExtendTestFixture extends CovariantOverloadTestFixture { + public CovariantOverloadExtendTestFixture(int x) { + super(x * 2); + } + + public CovariantOverloadExtendTestFixture foo(Number a, int b) { + return new CovariantOverloadExtendTestFixture(a.intValue() - b); + } +} \ No newline at end of file diff --git a/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java b/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java new file mode 100644 index 0000000000..5d1db8afd3 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Charles P. Wright + */ +@SuppressWarnings("UnusedDeclaration") +public class CovariantOverloadTestFixture { + int x; + + public CovariantOverloadTestFixture(int x) { + this.x = x; + } + + public CovariantOverloadTestFixture foo(Number a, int b) { + return new CovariantOverloadTestFixture(a.intValue() + b); + } + + public int getX() { + return x; + } +} \ No newline at end of file diff --git a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java index cec174ad66..ac58beed71 100644 --- a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java +++ b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java @@ -12,6 +12,9 @@ * 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. + * + * This file was modified by Illumon. + * */ package org.jpy.fixtures; @@ -29,6 +32,30 @@ public int throwNpeIfArgIsNull(String arg) { return arg.length(); } + public int throwNpeIfArgIsNull2(String arg) { + return throwNpeIfArgIsNull(arg); + } + + public int throwNpeIfArgIsNullNested(String arg) { + try { + return throwNpeIfArgIsNull(arg); + } catch (Exception e) { + throw new RuntimeException("Nested exception", e); + } + } + + public int throwNpeIfArgIsNullNested2(String arg) { + return throwNpeIfArgIsNullNested(arg); + } + + public int throwNpeIfArgIsNullNested3(String arg) { + try { + return throwNpeIfArgIsNullNested2(arg); + } catch (Exception e) { + throw new RuntimeException("Nested exception 3", e); + } + } + public int throwAioobeIfIndexIsNotZero(int index) { int[] ints = new int[]{101}; return ints[index]; diff --git a/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java b/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java index ed4455e400..4d07891bf0 100644 --- a/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java +++ b/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java @@ -72,6 +72,16 @@ public String join(String a, String b, String c) { return stringifyArgs(a, b, c); } + ////////////////////////////////////////////// + public String join2(Comparable a, int b, String c, String d) { + return stringifyArgs(a, b, c, d); + } + + ////////////////////////////////////////////// + public String join3(Number a, int b) { + return stringifyArgs(a, b); + } + /** * Used to test that we also find overloaded methods in class hierarchies */ diff --git a/src/test/java/org/jpy/fixtures/Processor.java b/src/test/java/org/jpy/fixtures/Processor.java index 29332fc372..08835f88d8 100644 --- a/src/test/java/org/jpy/fixtures/Processor.java +++ b/src/test/java/org/jpy/fixtures/Processor.java @@ -27,4 +27,10 @@ public interface Processor { String computeTile(int w, int h, float[] data); String dispose(); + + void setVal(int n); + + int getVal(); + + boolean check1234(); } diff --git a/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java new file mode 100644 index 0000000000..f52b00919f --- /dev/null +++ b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ + +package org.jpy.fixtures; + +/** + * Used as a test class for the test cases in jpy_retval_test.py + * Note: Please make sure to not add any method overloads to this class. + * This is done in {@link MethodOverloadTestFixture}. + * + * @author Norman Fomferra + */ +@SuppressWarnings("UnusedDeclaration") +public class TypeTranslationTestFixture { + public Thing makeThing(int value) { + return new Thing(value); + } +} diff --git a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java new file mode 100644 index 0000000000..230fdf2008 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * 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. + * + * This file was modified by Illumon. + * + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Norman Fomferra + */ +@SuppressWarnings("UnusedDeclaration") +public class VarArgsTestFixture { + + public String join(String prefix, int ... a) { + return stringifyArgs(prefix, a); + } + + public String join(String prefix, double ... a) { + return stringifyArgs(prefix, a); + } + public String join(String prefix, float ... a) { + return stringifyArgs(prefix, a); + } + public String join(String prefix, String ... a) { + return stringifyArgs(prefix, a); + } + + public String joinFloat(String prefix, float ... a) { + return stringifyArgs(prefix, a); + } + + public String joinLong(String prefix, long ... a) { + return stringifyArgs(prefix, a); + } + public String joinShort(String prefix, short ... a) { + return stringifyArgs(prefix, a); + } + public String joinByte(String prefix, byte ... a) { + return stringifyArgs(prefix, a); + } + public String joinChar(String prefix, char ... a) { + return stringifyArgs(prefix, a); + } + public String joinBoolean(String prefix, boolean ... a) { + return stringifyArgs(prefix, a); + } + public String joinObjects(String prefix, Object ... a) { + return stringifyArgs(prefix, a); + } + + public int chooseFixedArity(int... a) { + return 2; + } + + public int chooseFixedArity() { + return 1; + } + + public int stringOrObjectVarArgs(String ... a) { + return 1 + a.length; + } + public int stringOrObjectVarArgs(Object ... a) { + return 2 + a.length; + } + + static String stringifyArgs(Object... args) { + StringBuilder argString = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + argString.append(","); + } + Object arg = args[i]; + if (arg != null) { + Class argClass = arg.getClass(); + argString.append(argClass.getSimpleName()); + argString.append('('); + if (argClass.isArray()) { + stringifyArray(arg, argString); + } else { + stringifyObject(arg, argString); + } + argString.append(')'); + } else { + argString.append("null"); + } + } + return argString.toString(); + } + + private static void stringifyObject(Object arg, StringBuilder argString) { + argString.append(String.valueOf(arg)); + } + + private static void stringifyArray(Object arg, StringBuilder argString) { + boolean primitive = arg.getClass().getComponentType().isPrimitive(); + int length = Array.getLength(arg); + for (int i = 0; i < length; i++) { + Object item = Array.get(arg, i); + if (i > 0) { + argString.append(","); + } + if (primitive) { + argString.append(String.valueOf(item)); + } else { + argString.append(stringifyArgs(item)); + } + } + } + + +} diff --git a/src/test/python/fixtures/hasheqstr.py b/src/test/python/fixtures/hasheqstr.py new file mode 100644 index 0000000000..59ef87da25 --- /dev/null +++ b/src/test/python/fixtures/hasheqstr.py @@ -0,0 +1,29 @@ +class Simple(object): + def __init__(self, v): + self._v = v + + def __str__(self): + return "Simple: " + str(self._v) + + def getValue(self): + return self._v + +class HashSimple(object): + def __init__(self, v): + self._v = v + + def __str__(self): + return "HashSimple: " + str(self._v) + + def getValue(self): + return self._v + + def __hash__(self): + return hash(self._v) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self._v == other._v + else: + return False # Java can't support NotImplemented + diff --git a/src/test/python/fixtures/proc_class.py b/src/test/python/fixtures/proc_class.py index ce61510d8c..8e4e20df68 100644 --- a/src/test/python/fixtures/proc_class.py +++ b/src/test/python/fixtures/proc_class.py @@ -19,5 +19,12 @@ def spend_some_time(self): for i in range(10000): l.reverse() - - + def setVal(self, val): + self._val = val + + def getVal(self): + return self._val + + def check1234(self): + return self._val == 1234 + \ No newline at end of file diff --git a/src/test/python/fixtures/proc_module.py b/src/test/python/fixtures/proc_module.py index 63e633c4af..01f75e6b12 100644 --- a/src/test/python/fixtures/proc_module.py +++ b/src/test/python/fixtures/proc_module.py @@ -15,6 +15,15 @@ def spend_some_time(): for i in range(10000): l.reverse() - - - +_module_val = None +def setVal(val): + global _module_val + _module_val = val + +def getVal(): + global _module_val + return _module_val + +def check1234(): + global _module_val + return _module_val == 1234 diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index d5bd20c249..3888aeee0c 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -1,12 +1,12 @@ +# This file was modified by Illumon. import unittest import jpyutil - +import string jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) import jpy - class TestExceptions(unittest.TestCase): def setUp(self): self.Fixture = jpy.get_type('org.jpy.fixtures.ExceptionTestFixture') @@ -53,6 +53,44 @@ def test_IOException(self): fixture.throwIoeIfMessageIsNotNull("Evil!") self.assertEqual(str(e.exception), 'java.io.IOException: Evil!') + # Checking the exceptions for differences (e.g. in white space) can be a huge pain, this helps) + def hexdump(self, s): + for i in xrange(0, len(s), 32): + sl = s[i:min(i + 32, len(s))] + fsl = map(lambda x : x if (x in string.printable and not x in "\n\t\r") else ".", sl) + print "%08d %s %s %s" % (i, " ".join("{:02x}".format(ord(c)) for c in sl), (" ".join(map(lambda x : "", xrange(32 - len(sl))))), sl) + + def test_VerboseException(self): + fixture = self.Fixture() + + jpy.VerboseExceptions.enabled = True + + self.assertEqual(fixture.throwNpeIfArgIsNull("123456"), 6) + + with self.assertRaises(RuntimeError) as e: + fixture.throwNpeIfArgIsNullNested(None) + actualMessage = str(e.exception) + expectedMessage = "java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:43)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:32)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:41)\n" + + #self.hexdump(actualMessage) + #self.hexdump(expectedMessage) + #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] + + self.assertEquals(actualMessage, expectedMessage) + + with self.assertRaises(RuntimeError) as e: + fixture.throwNpeIfArgIsNullNested3(None) + actualMessage = str(e.exception) + expectedMessage = "java.lang.RuntimeException: Nested exception 3\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:55)\ncaused by java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:43)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested2(ExceptionTestFixture.java:48)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:53)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:32)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:41)\n\t... 2 more\n" + + #self.hexdump(actualMessage) + #self.hexdump(expectedMessage) + #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] + + self.assertEquals(actualMessage, expectedMessage) + + jpy.VerboseExceptions.enabled = False + if __name__ == '__main__': print('\nRunning ' + __file__) diff --git a/src/test/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 4a7c774418..b9be7a1d72 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -1,3 +1,4 @@ +# This file was modified by Illumon. import unittest import jpyutil @@ -84,16 +85,82 @@ def test_nArgOverloadsAreFoundInBaseClass(self): fixture.join('x', 'y', 'z', 'u', 'v') self.assertEqual(str(e.exception), 'no matching Java method overloads found') + def test_stringAsComparable(self): + fixture = self.Fixture() + self.assertEqual(fixture.join2("a", 1, "c", "d"), 'String(a),Integer(1),String(c),String(d)') + + def test_stringAsNumber(self): + fixture = self.Fixture() + with self.assertRaises(RuntimeError, msg='RuntimeError expected') as e: + fixture.join3('x', 2) + self.assertEqual(str(e.exception), 'no matching Java method overloads found') + + def test_numbersAsNumber(self): + fixture = self.Fixture() + self.assertEqual(fixture.join3(1, 2), 'Integer(1),Integer(2)') + self.assertEqual(fixture.join3(1.1, 2), 'Double(1.1),Integer(2)') + + def test_numbersAsComparable(self): + fixture = self.Fixture() + self.assertEqual(fixture.join2(1, 2, "c", "d"), 'Integer(1),Integer(2),String(c),String(d)') + self.assertEqual(fixture.join2(1.1, 2, "c", "d"), 'Double(1.1),Integer(2),String(c),String(d)') + +class TestVarArgs(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.VarArgsTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_varargsEmpty(self): + fixture = self.Fixture() + + self.assertEqual(fixture.joinFloat("Prefix"), 'String(Prefix),float[]()') + + with self.assertRaises(RuntimeError, msg='RuntimeError expected') as e: + res = fixture.join("Prefix") + self.assertEqual(str(e.exception), 'ambiguous Java method call, too many matching method overloads found') + + def test_varargs(self): + fixture = self.Fixture() + + self.assertEqual(fixture.join("Prefix", "a", "b", "c"), 'String(Prefix),String[](String(a),String(b),String(c))') + self.assertEqual(fixture.join("Prefix", 1, 2, 3), 'String(Prefix),int[](1,2,3)') + self.assertEqual(fixture.join("Prefix", 1.1, 2.1, 3.1), 'String(Prefix),double[](1.1,2.1,3.1)') + + self.assertEqual(fixture.joinFloat("Prefix", 1.1, 2.1, 3.1), 'String(Prefix),float[](1.1,2.1,3.1)') + + self.assertEqual(fixture.joinLong("Prefix", 1, 2, 3), 'String(Prefix),long[](1,2,3)') + bignum = 8589934592 + self.assertEqual(fixture.joinLong("Prefix", 1, 2, 3, bignum), 'String(Prefix),long[](1,2,3,'+str(bignum)+')') + + self.assertEqual(fixture.joinByte("Prefix", 1, 2, 3), 'String(Prefix),byte[](1,2,3)') + self.assertEqual(fixture.joinShort("Prefix", 1, 2, 3, 4), 'String(Prefix),short[](1,2,3,4)') + self.assertEqual(fixture.joinChar("Prefix", 65, 66), 'String(Prefix),char[](A,B)') + + self.assertEqual(fixture.joinBoolean("Prefix", True, False), 'String(Prefix),boolean[](true,false)') + self.assertEqual(fixture.joinObjects("Prefix", True, "A String", 3), 'String(Prefix),Object[](Boolean(true),String(A String),Integer(3))') + + def test_fixedArity(self): + fixture = self.Fixture() + + self.assertEqual(fixture.chooseFixedArity(), 1) + self.assertEqual(fixture.chooseFixedArity(1), 2) + self.assertEqual(fixture.chooseFixedArity(1, 2), 2) + + def test_stringVsObject(self): + fixture = self.Fixture() + self.assertEqual(fixture.stringOrObjectVarArgs(["a", "b"]), 3) + self.assertEqual(fixture.stringOrObjectVarArgs([1, 2, 3]), 5) + + class TestOtherMethodResolutionCases(unittest.TestCase): # see https://github.com/bcdev/jpy/issues/55 def test_toReproduceAndFixIssue55(self): Paths = jpy.get_type('java.nio.file.Paths') - # The following outcommented statement is will end in a Python error - # RuntimeError: no matching Java method overloads found - #p = Paths.get('testfile') - # This is the embarrassing workaround + # The following statement will execute the var args method without any arguments + p = Paths.get('testfile') + # This is the workaround that was previously required p = Paths.get('testfile', []) # see https://github.com/bcdev/jpy/issues/56 @@ -136,10 +203,20 @@ def setUp(self): # see https://github.com/bcdev/jpy/issues/102 def test_defaultedInterfaces(self): - fixture = self.Fixture() + fixture = self.Fixture() self.assertEqual(fixture.doItPlusOne(), 3) +class TestCovariantReturn(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.CovariantOverloadExtendTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_covariantReturn(self): + fixture = self.Fixture(1) + self.assertEqual(fixture.foo(4, 1).getX(), 6) + + if __name__ == '__main__': print('\nRunning ' + __file__) unittest.main() diff --git a/src/test/python/jpy_translation_test.py b/src/test/python/jpy_translation_test.py new file mode 100644 index 0000000000..ca64cf47da --- /dev/null +++ b/src/test/python/jpy_translation_test.py @@ -0,0 +1,42 @@ +# This file was modified by Illumon. +import unittest + +import jpyutil + +jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) +import jpy + +class DummyWrapper: + def __init__(self, theThing): + self.theThing = theThing + + def getValue(self): + return 2 * self.theThing.getValue() + +def make_wrapper(type, thing): + return DummyWrapper(thing) + + +class TestTypeTranslation(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.TypeTranslationTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_Translation(self): + fixture = self.Fixture() + thing = fixture.makeThing(7) + self.assertEqual(thing.getValue(), 7) + self.assertEquals(repr(type(thing)), "") + + jpy.type_translations['org.jpy.fixtures.Thing'] = make_wrapper + thing = fixture.makeThing(8) + self.assertEqual(thing.getValue(), 16) + self.assertEqual(type(thing), type(DummyWrapper(None))) + + jpy.type_translations['org.jpy.fixtures.Thing'] = None + self.assertEqual(fixture.makeThing(9).getValue(), 9) + + +if __name__ == '__main__': + print('\nRunning ' + __file__) + unittest.main()