From 1b7e59e3078f9d235a758dc3001008cdd65df54c Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Wed, 14 Jun 2017 09:16:48 -0400 Subject: [PATCH 01/42] Handle varargs method calls. 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. Additionally, a jpy.type_translations dictionary is available with allows you to easily wrap or modify all created objects of a particular java type. --- doc/reference.rst | 7 + setup.py | 3 +- src/main/c/jpy_jmethod.c | 170 ++++++-- src/main/c/jpy_jmethod.h | 9 +- src/main/c/jpy_jobj.c | 27 +- src/main/c/jpy_jobj.h | 4 +- src/main/c/jpy_jtype.c | 373 +++++++++++++++++- src/main/c/jpy_jtype.h | 4 + src/main/c/jpy_module.c | 8 + src/main/c/jpy_module.h | 2 + .../fixtures/TypeTranslationTestFixture.java | 31 ++ .../org/jpy/fixtures/VarArgsTestFixture.java | 111 ++++++ src/test/python/jpy_overload_test.py | 40 +- src/test/python/jpy_translation_test.py | 41 ++ 14 files changed, 769 insertions(+), 61 deletions(-) create mode 100644 src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java create mode 100644 src/test/java/org/jpy/fixtures/VarArgsTestFixture.java create mode 100644 src/test/python/jpy_translation_test.py diff --git a/doc/reference.rst b/doc/reference.rst index bb6fd55f3b..c8c4bfae7c 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -218,6 +218,13 @@ 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:: diag :module: jpy diff --git a/setup.py b/setup.py index 0aea809b08..4853ea9b82 100644 --- a/setup.py +++ b/setup.py @@ -98,6 +98,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 +201,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/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 050248c0ed..a972cf09b0 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -29,6 +29,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 +42,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); @@ -85,7 +87,7 @@ void JMethod_Del(JPy_JMethod* method) * Returns the sum of the i-th argument against the i-th Java parameter. * The maximum match value returned is 100 * method->paramCount. */ -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 +95,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 +166,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 +181,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 +237,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 +247,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 +377,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 +396,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 +425,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 +439,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 +693,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; @@ -640,25 +725,26 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* matchValueMax = -1; bestMethod = NULL; - 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); + 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\n", i, - currMethod->paramCount, matchValue); + 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 +754,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 +788,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 +797,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 +832,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 { @@ -795,15 +888,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..cdef79b2f3 100644 --- a/src/main/c/jpy_jmethod.h +++ b/src/main/c/jpy_jmethod.h @@ -38,6 +38,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 +75,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,17 +86,18 @@ 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 } /* extern "C" */ #endif -#endif /* !JPY_JMETHOD_H */ \ No newline at end of file +#endif /* !JPY_JMETHOD_H */ diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index a9fda7489c..fe1f5baadc 100644 --- a/src/main/c/jpy_jobj.c +++ b/src/main/c/jpy_jobj.c @@ -23,7 +23,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 +38,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 +66,19 @@ JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) array->bufferExportCount = 0; } - return obj; + 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; } /** @@ -101,12 +116,14 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) return -1; } - jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE); + int isVarArgsArray; + + 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..e5104f18c8 100644 --- a/src/main/c/jpy_jobj.h +++ b/src/main/c/jpy_jobj.h @@ -37,8 +37,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..38ba462403 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -34,7 +34,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 +42,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; @@ -870,7 +876,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 +884,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 +907,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 +977,7 @@ int JType_ProcessClassConstructors(JNIEnv* jenv, JPy_JType* type) jint constrCount; jint i; jboolean isPublic; + jboolean isVarArg; jmethodID mid; PyObject* methodKey; @@ -985,10 +992,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,6 +1073,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) jint methodCount; jint i; jboolean isStatic; + jboolean isVarArg; jboolean isPublic; const char* methodName; jmethodID mid; @@ -1083,6 +1092,7 @@ 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; + isVarArg = (modifiers & 0x0080) != 0; if (isPublic) { methodNameStr = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetName_MID); returnType = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetReturnType_MID); @@ -1091,7 +1101,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 +1237,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 +1362,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 +1502,326 @@ 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; + if (componentType == NULL) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + + int minMatch = 100; + for (int 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; + if (componentType != JPy_JString) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + + int minMatch = 100; + for (int 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; + if (componentType != JPy_JBoolean) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + + int minMatch = 100; + for (int 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; + if (componentType != expectedComponentType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + + int minMatch = 100; + for (int 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 didentical, 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; + if (componentType != expectedType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + + int minMatch = 100; + for (int 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: negative 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) < 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; @@ -1839,7 +2171,7 @@ void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data) } } -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean lastVarArg) { JPy_JType* paramType = paramDescriptor->type; @@ -1880,6 +2212,31 @@ void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) paramDescriptor->MatchPyArg = JType_MatchPyArgAsJObjectParam; paramDescriptor->ConvertPyArg = JType_ConvertPyArgToJObjectArg; } + if (lastVarArg) { + 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..590c337f40 100644 --- a/src/main/c/jpy_jtype.h +++ b/src/main/c/jpy_jtype.h @@ -74,7 +74,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 +105,9 @@ typedef struct JPy_ParamDescriptor jboolean isOutput; jboolean isReturn; JPy_MatchPyArg MatchPyArg; + JPy_MatchVarArgPyArg MatchVarArgPyArg; JPy_ConvertPyArg ConvertPyArg; + JPy_ConvertVarArgPyArg ConvertVarArgPyArg; } JPy_ParamDescriptor; diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index a5c25df195..2c638c45d3 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -85,6 +85,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. @@ -324,6 +325,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); } @@ -987,6 +994,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..eb62de55b8 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -32,6 +32,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 +43,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" /** 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..ae7b2b2eb1 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +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..39c8237754 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java @@ -0,0 +1,111 @@ +/* + * 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. + */ + +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); + } + + 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/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 4a7c774418..1d98acb38e 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -84,16 +84,48 @@ def test_nArgOverloadsAreFoundInBaseClass(self): fixture.join('x', 'y', 'z', 'u', 'v') self.assertEqual(str(e.exception), 'no matching Java method overloads found') +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: + 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))') 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 diff --git a/src/test/python/jpy_translation_test.py b/src/test/python/jpy_translation_test.py new file mode 100644 index 0000000000..9fb095edbd --- /dev/null +++ b/src/test/python/jpy_translation_test.py @@ -0,0 +1,41 @@ +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() From 2615f1fe9e9e03b459e9ad04ba738048f9cbd0e7 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Tue, 12 Sep 2017 13:24:53 -0400 Subject: [PATCH 02/42] Take fixed arity matches over varargs. --- src/main/c/jpy_jmethod.c | 29 ++++++++++++++++++- .../org/jpy/fixtures/VarArgsTestFixture.java | 8 +++++ src/test/python/jpy_overload_test.py | 10 ++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index a972cf09b0..3a8083883e 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -724,12 +724,19 @@ 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, 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); + + 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, @@ -867,7 +874,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); + if (method->isVarArgs) { + return PyList_Append(overloadedMethod->methodList, (PyObject *) method); + } else { + // we need to insert this before the first varargs method + Py_ssize_t size = PyList_Size(overloadedMethod->methodList); + Py_ssize_t destinationIndex = -1; + for (Py_ssize_t 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); + } + } } /** diff --git a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java index 39c8237754..1aa8303f4d 100644 --- a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java @@ -63,6 +63,14 @@ public String joinObjects(String prefix, Object ... a) { return stringifyArgs(prefix, a); } + public int chooseFixedArity(int... a) { + return 2; + } + + public int chooseFixedArity() { + return 1; + } + static String stringifyArgs(Object... args) { StringBuilder argString = new StringBuilder(); for (int i = 0; i < args.length; i++) { diff --git a/src/test/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 1d98acb38e..eb078a043c 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -95,7 +95,7 @@ def test_varargsEmpty(self): self.assertEqual(fixture.joinFloat("Prefix"), 'String(Prefix),float[]()') with self.assertRaises(RuntimeError, msg='RuntimeError expected') as e: - fixture.join("Prefix") + res = fixture.join("Prefix") self.assertEqual(str(e.exception), 'ambiguous Java method call, too many matching method overloads found') def test_varargs(self): @@ -118,6 +118,14 @@ def test_varargs(self): 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) + + class TestOtherMethodResolutionCases(unittest.TestCase): # see https://github.com/bcdev/jpy/issues/55 From 69da2cddd367dd493e3b70111d58f9eeed4ad937 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Tue, 12 Sep 2017 17:58:09 -0400 Subject: [PATCH 03/42] Verbose exceptions. --- setup.py | 1 + src/main/c/jpy_module.c | 159 ++++++++++++++++-- src/main/c/jpy_verboseexcept.c | 95 +++++++++++ src/main/c/jpy_verboseexcept.h | 34 ++++ .../jpy/fixtures/ExceptionTestFixture.java | 12 ++ src/test/python/jpy_exception_test.py | 20 ++- 6 files changed, 308 insertions(+), 13 deletions(-) create mode 100644 src/main/c/jpy_verboseexcept.c create mode 100644 src/main/c/jpy_verboseexcept.h diff --git a/setup.py b/setup.py index 4853ea9b82..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'), diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 2c638c45d3..702ae63847 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -16,6 +16,7 @@ #include "jpy_module.h" #include "jpy_diag.h" +#include "jpy_verboseexcept.h" #include "jpy_jtype.h" #include "jpy_jmethod.h" #include "jpy_jfield.h" @@ -120,6 +121,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 @@ -204,6 +207,15 @@ jmethodID JPy_PyObject_GetPointer_MID = NULL; jmethodID JPy_PyObject_Init_MID = NULL; jmethodID JPy_PyModule_Init_MID = NULL; +// java.lang.Throwable +jclass JPy_Throwable_JClass = NULL; +jmethodID JPy_Throwable_getMessage_MID = NULL; +jmethodID JPy_Throwable_getStackTrace_MID = NULL; +jmethodID JPy_Throwable_getCause_MID = NULL; + +// stack trace element +jclass JPy_StackTraceElement_JClass = NULL; + // }}} @@ -341,6 +353,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) { @@ -795,6 +816,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); @@ -821,6 +844,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: @@ -954,31 +981,141 @@ void JPy_ClearGlobalVars(JNIEnv* jenv) JPy_JPyModule = NULL; } +#define AT_STRING "\t at " +#define AT_STRLEN 5 +#define CAUSED_BY_STRING "caused by " +#define CAUSED_BY_STRLEN 10 + 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; + int stackTraceLength = 0; + + stackTraceString = strdup(""); + + jthrowable cause = error; + do { + /* We want the type and the detail string, which is actually what a Throwable toString() does by + * default, as does the default printStackTrace(). */ + + 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. */ + jarray stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID); + + jint stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace); + for (jint ii = 0; ii < stackTraceElements; ++ii) { + jobject traceElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, ii); + if (traceElement != NULL) { + message = (jstring) (*jenv)->CallObjectMethod(jenv, traceElement, JPy_Object_ToString_MID); + if (message != NULL) { + const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + allocError = 1; + break; + } + + size_t len = strlen(messageChars); + + char *newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength); + if (newStackString == NULL) { + 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); + } + + } + } + (*jenv)->DeleteLocalRef(jenv, stackTrace); + + /** 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); diff --git a/src/main/c/jpy_verboseexcept.c b/src/main/c/jpy_verboseexcept.c new file mode 100644 index 0000000000..e1904cb566 --- /dev/null +++ b/src/main/c/jpy_verboseexcept.c @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#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) +{ + //printf("Diag_setattro: attr_name=%s\n", JPy_AS_UTF8(attr_name)); + 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..3ab4a8a156 --- /dev/null +++ b/src/main/c/jpy_verboseexcept.h @@ -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. + */ + +#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/test/java/org/jpy/fixtures/ExceptionTestFixture.java b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java index cec174ad66..43b531f702 100644 --- a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java +++ b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java @@ -29,6 +29,18 @@ 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 throwAioobeIfIndexIsNotZero(int index) { int[] ints = new int[]{101}; return ints[index]; diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index d5bd20c249..1d87f72cf6 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -2,11 +2,9 @@ import jpyutil - 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 +51,24 @@ def test_IOException(self): fixture.throwIoeIfMessageIsNotNull("Evil!") self.assertEqual(str(e.exception), 'java.io.IOException: Evil!') + 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) + if False: + self.assertRegexpMatches(str(e.exception), """java.lang.RuntimeException: Nested exception + at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:40) +caused by java.lang.NullPointerException + at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29) + at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n""") + + jpy.VerboseExceptions.enabled = False + if __name__ == '__main__': print('\nRunning ' + __file__) From 320783fc3c7e8a811ad28ae12f047cecf729d711 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Tue, 12 Sep 2017 18:56:21 -0400 Subject: [PATCH 04/42] Remove duplicate stack trace entries. --- src/main/c/jpy_module.c | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 702ae63847..7b841f254b 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -1005,6 +1005,10 @@ void JPy_HandleJavaException(JNIEnv* jenv) stackTraceString = strdup(""); jthrowable cause = error; + + jarray enclosingElements = NULL; + jint enclosingSize = 0; + do { /* We want the type and the detail string, which is actually what a Throwable toString() does by * default, as does the default printStackTrace(). */ @@ -1054,7 +1058,24 @@ void JPy_HandleJavaException(JNIEnv* jenv) jarray stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID); jint stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace); - for (jint ii = 0; ii < stackTraceElements; ++ii) { + jint lastElementToPrint = stackTraceElements - 1; + jint 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 (jint 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); @@ -1086,7 +1107,27 @@ void JPy_HandleJavaException(JNIEnv* jenv) } } - (*jenv)->DeleteLocalRef(jenv, stackTrace); + + if (lastElementToPrint < stackTraceElements - 1) { + char *newStackString = realloc(stackTraceString, stackTraceLength + 100); + if (newStackString == NULL) { + allocError = 1; + break; + } + stackTraceString[stackTraceLength + 100] = '\0'; + + stackTraceString = newStackString; + int written = snprintf(stackTraceString + stackTraceLength, 99, "\t... %d more\n", (stackTraceElements - lastElementToPrint) - 1); + if (written > 99) { + stackTraceLength += 99; + } 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); From bf3bd983fc0578a3def81f904e2764173cf4e531 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Thu, 14 Sep 2017 10:02:08 -0400 Subject: [PATCH 05/42] Add more introspection to PyObject, reuse globals for locals. --- src/main/c/jni/org_jpy_PyLib.c | 430 ++++++++++++++++++++++- src/main/c/jni/org_jpy_PyLib.h | 40 +++ src/main/java/org/jpy/PyDictWrapper.java | 183 ++++++++++ src/main/java/org/jpy/PyLib.java | 24 ++ src/main/java/org/jpy/PyListWrapper.java | 192 ++++++++++ src/main/java/org/jpy/PyObject.java | 152 +++++++- 6 files changed, 1018 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/jpy/PyDictWrapper.java create mode 100644 src/main/java/org/jpy/PyListWrapper.java diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index fb73941ab4..eba1db4336 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -390,7 +390,7 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode jStart == JPy_IM_SCRIPT ? Py_file_input : Py_eval_input; - pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyLocals); + pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyGlobals); if (pyReturnValue == NULL) { PyLib_HandlePythonException(jenv); goto error; @@ -414,6 +414,90 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode return (jlong) pyReturnValue; } +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) +{ + FILE *fp; + const char* fileChars; + PyObject* pyReturnValue; + PyObject* pyGlobals; + PyObject* pyLocals; + PyObject* pyMainModule; + int start; + + JPy_BEGIN_GIL_STATE + + fp = NULL; + pyGlobals = NULL; + pyLocals = NULL; + pyReturnValue = NULL; + pyMainModule = NULL; + fileChars = NULL; + + pyMainModule = PyImport_AddModule("__main__"); // borrowed ref + if (pyMainModule == NULL) { + PyLib_HandlePythonException(jenv); + goto error; + } + + fileChars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); + if (fileChars == NULL) { + // todo: Throw out-of-memory error + goto error; + } + + fp = fopen(fileChars, "r"); + if (!fp) { + goto error; + } + + pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref + if (pyGlobals == NULL) { + PyLib_HandlePythonException(jenv); + goto error; + } + + pyLocals = PyDict_New(); // new ref + if (pyLocals == NULL) { + 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) + + start = jStart == JPy_IM_STATEMENT ? Py_single_input : + jStart == JPy_IM_SCRIPT ? Py_file_input : + Py_eval_input; + + pyReturnValue = PyRun_File(fp, fileChars, start, pyGlobals, pyGlobals); + if (pyReturnValue == NULL) { + PyLib_HandlePythonException(jenv); + goto error; + } + + // 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); + +error: + if (fileChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jFile, fileChars); + } + if (fp != NULL) { + fclose(fp); + } + + Py_XDECREF(pyLocals); + + JPy_END_GIL_STATE + + return (jlong) pyReturnValue; +} + /* * Class: org_jpy_python_PyLib * Method: incRef @@ -492,6 +576,31 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue return value; } +/* + * Class: org_jpy_python_PyLib + * Method: getIntValue + * Signature: (J)I + */ +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 @@ -569,6 +678,267 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue return jObject; } +/* + * Class: org_jpy_python_PyLib + * Method: getObjectValue + * Signature: (J)Ljava/lang/Object; + */ +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; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getType + * Signature: (J)Lorg/jpy/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; +} + +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; +} + +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 + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyBool_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (Py_None == (((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyInt_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +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 { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyFloat_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyString_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyCallable_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getType + * Signature: (J)Lorg/jpy/PyObject; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; + jobject jObject; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject *) objId; + + PyObject *pyStr = PyObject_Str(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); + Py_DECREF(pyStr); + } else { + jObject = NULL; + } + + + JPy_END_GIL_STATE + + return jObject; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getType + * Signature: (J)Lorg/jpy/PyObject; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; + jobject jObject; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject *) objId; + + PyObject *pyStr = PyObject_Repr(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); + Py_DECREF(pyStr); + } else { + jObject = NULL; + } + + + JPy_END_GIL_STATE + + return jObject; +} + /* * Class: org_jpy_PyLib * Method: getObjectArrayValue @@ -771,6 +1141,64 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue JPy_END_GIL_STATE } +/* + * Class: org_jpy_python_PyLib + * Method: setAttributeValue + * Signature: (JLjava/lang/String;J)V + */ +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); + 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: + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + + JPy_END_GIL_STATE +} + +/* + * Class: org_jpy_python_PyLib + * Method: setAttributeValue + * Signature: (JLjava/lang/String;J)V + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute + (JNIEnv* jenv, jclass jLibClass, jlong objId, jstring jName) +{ + PyObject* pyObject; + const char* nameChars; + jboolean result; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + 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; + + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + + JPy_END_GIL_STATE + + return result; +} + /* * Class: org_jpy_python_PyLib diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index 33e40bbf7b..b9e8f87243 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -55,6 +55,9 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode (JNIEnv *, jclass, jstring, jint, jobject, jobject); +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv *, jclass, jstring, jint, jobject, jobject); + /* * Class: org_jpy_PyLib * Method: incRef @@ -79,6 +82,8 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue (JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_getBooleanValue + (JNIEnv* jenv, jclass jLibClass, jlong objId); /* * Class: org_jpy_PyLib * Method: getDoubleValue @@ -103,6 +108,41 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue (JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: getObjectValue + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType + (JNIEnv *, jclass, jlong); + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck(JNIEnv *, jclass, jlong); +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck(JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: getObjectValue + * Signature: (J)Ljava/lang/Object; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: getObjectValue + * Signature: (J)Ljava/lang/Object; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: getObjectArrayValue diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java new file mode 100644 index 0000000000..3a0f6d4274 --- /dev/null +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -0,0 +1,183 @@ +package org.jpy; + +import java.util.*; + +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(); + } + + @Override + public boolean containsValue(Object value) { + return pyObject.callMethod("values").asList().contains(value); + } + + @Override + public PyObject get(Object key) { + return pyObject.callMethod("__getitem__", key); + } + + @Override + public PyObject put(PyObject key, PyObject value) { + return pyObject.callMethod("__setitem__", key, value); + } + + @Override + public PyObject remove(Object key) { + PyObject value = get(key); + if (value.isNone()) { + return null; + } else { + pyObject.callMethod("__delitem__", key); + return value; + } + } + + @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(); + } + + 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) { + return false; + } + + @Override + public Iterator> iterator() { + return new Iterator>() { + PyObject it = pyObject.callMethod("__iter__"); + PyObject next = prepareNext(); + + private PyObject prepareNext() { + try { + return next = it.callMethod("next"); + } catch (Exception e) { + return next = null; + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Entry next() { + 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..4612c80e5c 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -234,18 +234,39 @@ public static void stopPython() { static native long executeCode(String code, int start, Map globals, Map locals); + static native long executeScript + (String file, int start, Map globals, Map locals); + static native void incRef(long pointer); static native void decRef(long pointer); 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 pyCallableCheck(long pointer); + + static native long getType(long pointer); + + static native String str(long pointer); + + static native String repr(long pointer); + static native T[] getObjectArrayValue(long pointer, Class itemType); static native long importModule(String name); @@ -284,6 +305,9 @@ public static void stopPython() { */ static native void setAttributeValue(long pointer, String name, T value, Class valueType); + static native void delAttribute(long pointer, String 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/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java new file mode 100644 index 0000000000..e17f19b648 --- /dev/null +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -0,0 +1,192 @@ +package org.jpy; + +import java.util.*; + +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/PyObject.java b/src/main/java/org/jpy/PyObject.java index a50f52cacb..3a1b3608ce 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -18,6 +18,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; +import java.util.List; import java.util.Map; import static org.jpy.PyLib.assertPythonRuns; @@ -54,6 +55,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) { + return executeScript(script, mode, null, null); + } + /** * Executes Python source code in the context specified by the {@code globals} and {@code locals} maps. *

@@ -76,6 +88,29 @@ public static PyObject executeCode(String code, PyInputMode mode, Map + * 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. + * @param locals The locals variables to be set. + * @return The result of executing the script as a Python object. + */ + public static PyObject executeScript(String script, PyInputMode mode, Map globals, Map locals) { + if (script == null) { + throw new NullPointerException("script must not be null"); + } + if (mode == null) { + throw new NullPointerException("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 +141,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 +178,75 @@ 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 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 Map 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. @@ -199,6 +311,32 @@ public void setAttribute(String name, T value) { PyLib.setAttributeValue(getPointer(), name, value, value != null ? value.getClass() : null); } + /** + * Sets the value of a Python attribute from the given Java object. + *

+ * If the Java {@code value} cannot be directly converted into a Python object, a Java wrapper will be created instead. + * 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. + */ + public void delAttribute(String name) { + assertPythonRuns(); + PyLib.delAttribute(getPointer(), name); + } + + /** + * Sets the value of a Python attribute from the given Java object. + *

+ * If the Java {@code value} cannot be directly converted into a Python object, a Java wrapper will be created instead. + * 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. + */ + public boolean hasAttribute(String name) { + assertPythonRuns(); + 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. @@ -278,14 +416,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); } /** From 1eac85436e41e4df279c421eec4dae0db6fdc400 Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Thu, 14 Sep 2017 16:01:18 -0400 Subject: [PATCH 06/42] Fix for toString test, and variable initialization. --- src/main/c/jpy_jtype.c | 15 ++++++++++----- src/main/c/jpy_module.c | 3 ++- src/test/java/org/jpy/PyObjectTest.java | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 38ba462403..0cfaa9d183 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1519,7 +1519,8 @@ int JType_MatchVarArgPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* para PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); int minMatch = 100; - for (int ii = 0; ii < remaining; ii++) { + int ii; + for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); int matchValue = JType_MatchPyArgAsJObject(jenv, componentType, unpack); if (matchValue == 0) { @@ -1547,7 +1548,8 @@ int JType_MatchVarArgPyArgAsJStringParam(JNIEnv* jenv, JPy_ParamDescriptor* para PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); int minMatch = 100; - for (int ii = 0; ii < remaining; ii++) { + int ii; + for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); int matchValue = JType_MatchPyArgAsJStringParam(jenv, paramDescriptor, unpack); if (matchValue == 0) { @@ -1576,7 +1578,8 @@ int JType_MatchVarArgPyArgAsJBooleanParam(JNIEnv *jenv, JPy_ParamDescriptor *par PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); int minMatch = 100; - for (int ii = 0; ii < remaining; ii++) { + int ii; + for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); int matchValue; @@ -1632,7 +1635,8 @@ int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, Py PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); int minMatch = 100; - for (int ii = 0; ii < remaining; ii++) { + int ii; + for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); int matchValue; @@ -1676,7 +1680,8 @@ int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, P PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); int minMatch = 100; - for (int ii = 0; ii < remaining; ii++) { + int ii; + for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); int matchValue; diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 7b841f254b..f41455479c 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -1012,6 +1012,7 @@ void JPy_HandleJavaException(JNIEnv* jenv) 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; if (stackTraceLength > 0) { char *newStackString; @@ -1075,7 +1076,7 @@ void JPy_HandleJavaException(JNIEnv* jenv) enclosingIndex--; } - for (jint ii = 0; ii <= lastElementToPrint; ++ii) { + 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); diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index b3e3fd880c..ea6cb0dcf9 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -66,7 +66,7 @@ public void testPointer() throws Exception { 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 From 6425cdd54a9ab9bb5d2dc678c0954924fcd526ab Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Tue, 19 Sep 2017 12:28:37 -0400 Subject: [PATCH 07/42] Comment cleanups. --- src/main/c/jni/org_jpy_PyLib.c | 153 ++++++++++++++++++++--- src/main/c/jpy_jmethod.c | 3 + src/main/c/jpy_jmethod.h | 2 +- src/main/c/jpy_jobj.c | 2 + src/main/c/jpy_jtype.c | 2 +- src/main/c/jpy_module.c | 1 - src/main/c/jpy_verboseexcept.c | 1 - src/main/java/org/jpy/PyDictWrapper.java | 4 + src/main/java/org/jpy/PyLib.java | 16 +++ src/main/java/org/jpy/PyListWrapper.java | 3 + src/main/java/org/jpy/PyObject.java | 4 +- 11 files changed, 166 insertions(+), 25 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index eba1db4336..67347b7dfe 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -339,7 +339,14 @@ void dumpDict(const char* dictName, PyObject* dict) } } - +/* + * Calls PyRun_String under the covers to execute a python script using the __main__ globals. + * + * 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. + * + * The jGLobals and jLocals parameters are ignored. + */ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode (JNIEnv* jenv, jclass jLibClass, jstring jCode, jint jStart, jobject jGlobals, jobject jLocals) { @@ -390,6 +397,8 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode jStart == JPy_IM_SCRIPT ? Py_file_input : Py_eval_input; + // by using the pyGlobals for the locals variable, we are able to execute Python code and + // retrieve values afterwards pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyGlobals); if (pyReturnValue == NULL) { PyLib_HandlePythonException(jenv); @@ -414,6 +423,15 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode return (jlong) pyReturnValue; } +/** + * Calls PyRun_File under the covers to execute a python script using the __main__ globals. + * + * 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. + * + * The jGLobals and jLocals parameters are ignored. + * + */ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) { @@ -578,8 +596,12 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue /* * Class: org_jpy_python_PyLib - * Method: getIntValue + * Method: getBooleanValue * Signature: (J)I + * + * 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) @@ -679,9 +701,10 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue } /* - * Class: org_jpy_python_PyLib - * Method: getObjectValue - * Signature: (J)Ljava/lang/Object; + * 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) @@ -706,6 +729,10 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible * Class: org_jpy_python_PyLib * Method: getType * Signature: (J)Lorg/jpy/PyObject; + * + * 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) @@ -721,6 +748,14 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType 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) { @@ -739,6 +774,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck return result; } +/** + * 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 list + */ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -757,6 +800,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck return result; } +/** + * 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) { @@ -775,6 +826,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck return result; } +/** + * 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 jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -793,6 +852,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck return result; } +/** + * 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 jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -811,6 +878,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck return result; } +/** + * 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) { @@ -829,6 +904,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck 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 jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -847,6 +930,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck return result; } +/** + * 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 jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -865,6 +956,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck return result; } +/** + * 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 jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -883,10 +982,13 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getType - * Signature: (J)Lorg/jpy/PyObject; +/** + * 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) { @@ -911,10 +1013,14 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str return jObject; } -/* - * Class: org_jpy_python_PyLib - * Method: getType - * Signature: (J)Lorg/jpy/PyObject; + +/** + * 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 jstring JNICALL Java_org_jpy_PyLib_repr (JNIEnv* jenv, jclass jLibClass, jlong objId) { @@ -1142,9 +1248,12 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue } /* - * Class: org_jpy_python_PyLib - * Method: setAttributeValue - * Signature: (JLjava/lang/String;J)V + * 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) @@ -1172,10 +1281,16 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute } /* - * Class: org_jpy_python_PyLib - * Method: setAttributeValue - * Signature: (JLjava/lang/String;J)V + * 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) { diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 3a8083883e..5968040f34 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -86,6 +86,9 @@ 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 *isVarArgArray) { diff --git a/src/main/c/jpy_jmethod.h b/src/main/c/jpy_jmethod.h index cdef79b2f3..961691f365 100644 --- a/src/main/c/jpy_jmethod.h +++ b/src/main/c/jpy_jmethod.h @@ -100,4 +100,4 @@ void JMethod_DisposeJArgs(JNIEnv* jenv, int paramCount, jvalue* jValues, JPy_Arg } /* extern "C" */ #endif -#endif /* !JPY_JMETHOD_H */ +#endif /* !JPY_JMETHOD_H */ \ No newline at end of file diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index fe1f5baadc..5841db9cfa 100644 --- a/src/main/c/jpy_jobj.c +++ b/src/main/c/jpy_jobj.c @@ -66,6 +66,8 @@ PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) array->bufferExportCount = 0; } + // 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)) { diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 0cfaa9d183..3197c25cf8 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1660,7 +1660,7 @@ int JType_MatchVarArgPyArgAsJFloatParam(JNIEnv *jenv, JPy_ParamDescriptor *param return JType_MatchVarArgPyArgAsFPType(paramDescriptor, pyArg, idx, JPy_JFloat, 90); } -/* The float and double match functions are almost didentical, but for the expected componentType and the match value +/* 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) { diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index f41455479c..05fd751759 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -209,7 +209,6 @@ jmethodID JPy_PyModule_Init_MID = NULL; // java.lang.Throwable jclass JPy_Throwable_JClass = NULL; -jmethodID JPy_Throwable_getMessage_MID = NULL; jmethodID JPy_Throwable_getStackTrace_MID = NULL; jmethodID JPy_Throwable_getCause_MID = NULL; diff --git a/src/main/c/jpy_verboseexcept.c b/src/main/c/jpy_verboseexcept.c index e1904cb566..41a5d5435e 100644 --- a/src/main/c/jpy_verboseexcept.c +++ b/src/main/c/jpy_verboseexcept.c @@ -37,7 +37,6 @@ PyObject* VerboseExceptions_getattro(PyObject* self, PyObject *attr_name) int VerboseExceptions_setattro(PyObject* self, PyObject *attr_name, PyObject *v) { - //printf("Diag_setattro: attr_name=%s\n", JPy_AS_UTF8(attr_name)); if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) { if (PyBool_Check(v)) { JPy_VerboseExceptions = v == Py_True; diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 3a0f6d4274..a00c15f97a 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -2,6 +2,10 @@ 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; diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index 4612c80e5c..bc6e0be22d 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -305,7 +305,23 @@ 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); /** diff --git a/src/main/java/org/jpy/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java index e17f19b648..dbe6fcb947 100644 --- a/src/main/java/org/jpy/PyListWrapper.java +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -2,6 +2,9 @@ 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; diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index 3a1b3608ce..1dcce572e2 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -423,7 +423,7 @@ public Object createProxy(PyLib.CallableKind callableKind, Class... types) { */ @Override public final String toString() { - return PyLib.str(pointer); + return PyLib.str(pointer); } /** @@ -433,7 +433,7 @@ public final String toString() { * @see #getPointer() */ public final String repr() { - return PyLib.repr(pointer); + return PyLib.repr(pointer); } /** From 9c6eacb5e7ddd5c21ec8c509207b91e9d0d9241d Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Wed, 27 Sep 2017 18:27:30 -0400 Subject: [PATCH 08/42] Change variable name. --- src/main/c/jpy_jtype.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 3197c25cf8..50411ee75e 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -2176,7 +2176,7 @@ void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data) } } -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean lastVarArg) +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean isLastVarArg) { JPy_JType* paramType = paramDescriptor->type; @@ -2217,7 +2217,7 @@ void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jb paramDescriptor->MatchPyArg = JType_MatchPyArgAsJObjectParam; paramDescriptor->ConvertPyArg = JType_ConvertPyArgToJObjectArg; } - if (lastVarArg) { + if (isLastVarArg) { paramDescriptor->ConvertVarArgPyArg = JType_ConvertVarArgPyArgToJObjectArg; if (paramType->componentType == JPy_JBoolean) { From c85d72294b78e71df17b5e2eeefdcb6f40598fd3 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Wed, 27 Sep 2017 18:30:00 -0400 Subject: [PATCH 09/42] Combine append calls.. --- src/main/c/jpy_jmethod.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 5968040f34..09ecb1e535 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -877,12 +877,11 @@ JPy_JOverloadedMethod* JOverloadedMethod_New(JPy_JType* declaringClass, PyObject int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMethod* method) { - if (method->isVarArgs) { - return PyList_Append(overloadedMethod->methodList, (PyObject *) method); - } else { + Py_ssize_t destinationIndex = -1; + + if (!method->isVarArgs) { // we need to insert this before the first varargs method Py_ssize_t size = PyList_Size(overloadedMethod->methodList); - Py_ssize_t destinationIndex = -1; for (Py_ssize_t ii = 0; ii < size; ii++) { PyObject *check = PyList_GetItem(overloadedMethod->methodList, ii); if (((JPy_JMethod *) check)->isVarArgs) { @@ -891,12 +890,12 @@ int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMe break; } } + } - if (destinationIndex >= 0) { - return PyList_Insert(overloadedMethod->methodList, destinationIndex, (PyObject *) method); - } else { - return PyList_Append(overloadedMethod->methodList, (PyObject *) method); - } + if (destinationIndex >= 0) { + return PyList_Insert(overloadedMethod->methodList, destinationIndex, (PyObject *) method); + } else { + return PyList_Append(overloadedMethod->methodList, (PyObject *) method); } } From 53ae338ced6d36774ef3a5215ee7d16518e0eceb Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 08:17:35 -0400 Subject: [PATCH 10/42] Copy and expand comment. --- src/main/c/jni/org_jpy_PyLib.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 67347b7dfe..509dc55c7a 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -399,6 +399,12 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode // by using the pyGlobals for the locals variable, we are able to execute Python code and // retrieve values afterwards + // + // 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. pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyGlobals); if (pyReturnValue == NULL) { PyLib_HandlePythonException(jenv); @@ -489,6 +495,14 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript jStart == JPy_IM_SCRIPT ? Py_file_input : Py_eval_input; + // by using the pyGlobals for the locals variable, we are able to execute Python code and + // retrieve values afterwards + // + // 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. pyReturnValue = PyRun_File(fp, fileChars, start, pyGlobals, pyGlobals); if (pyReturnValue == NULL) { PyLib_HandlePythonException(jenv); From 21ccf81bb45a68aaac8e56d70e60fd0235375e5a Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 09:42:58 -0400 Subject: [PATCH 11/42] GetStringUTFChars result checking. --- src/main/c/jni/org_jpy_PyLib.c | 82 ++++++++++++++++++++++++++++------ src/main/c/jpy_module.c | 2 + src/main/c/jpy_module.h | 1 + 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 509dc55c7a..b7a33ec7e1 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -32,6 +32,7 @@ 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_RedirectStdOut(void); static int JPy_InitThreads = 0; @@ -291,18 +292,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 @@ -373,7 +382,7 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode codeChars = (*jenv)->GetStringUTFChars(jenv, jCode, NULL); if (codeChars == NULL) { - // todo: Throw out-of-memory error + PyLib_ThrowOOM(jenv); goto error; } @@ -466,7 +475,7 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript fileChars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); if (fileChars == NULL) { - // todo: Throw out-of-memory error + PyLib_ThrowOOM(jenv); goto error; } @@ -1130,12 +1139,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); @@ -1145,7 +1158,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 @@ -1229,6 +1246,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) { @@ -1256,7 +1277,9 @@ 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 } @@ -1280,6 +1303,10 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute 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) { @@ -1289,7 +1316,10 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute } error: - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } + JPy_END_GIL_STATE } @@ -1310,18 +1340,25 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute { PyObject* pyObject; const char* nameChars; - jboolean result; + 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; - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } JPy_END_GIL_STATE @@ -1416,10 +1453,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); @@ -1427,7 +1468,10 @@ 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; } @@ -1436,7 +1480,7 @@ PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean i PyObject* pyCallable; PyObject* pyArgs; PyObject* pyArg; - PyObject* pyReturnValue; + PyObject* pyReturnValue = Py_None; const char* nameChars; jint i; jobject jArg; @@ -1446,6 +1490,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); @@ -1526,7 +1574,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); @@ -1672,6 +1722,10 @@ void PyLib_HandlePythonException(JNIEnv* jenv) PyErr_Clear(); } +void PyLib_ThrowOOM(JNIEnv* jenv) { + (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, "Out of memory"); +} + //////////////////////////////////////////////////////////////////////////////////////////////// // Redirect stdout diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 05fd751759..6b6b191cf0 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -166,6 +166,7 @@ jmethodID JPy_Field_GetModifiers_MID = NULL; jmethodID JPy_Field_GetType_MID = NULL; jclass JPy_RuntimeException_JClass = NULL; +jclass JPy_OutOfMemoryError_JClass = NULL; // java.lang.Boolean jclass JPy_Boolean_JClass = NULL; @@ -780,6 +781,7 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_METHOD(JPy_Method_GetReturnType_MID, JPy_Method_JClass, "getReturnType", "()Ljava/lang/Class;"); DEFINE_CLASS(JPy_RuntimeException_JClass, "java/lang/RuntimeException"); + DEFINE_CLASS(JPy_OutOfMemoryError_JClass, "java/lang/OutOfMemoryError"); DEFINE_CLASS(JPy_Boolean_JClass, "java/lang/Boolean"); DEFINE_METHOD(JPy_Boolean_Init_MID, JPy_Boolean_JClass, "", "(Z)V"); diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index eb62de55b8..abcb093625 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -152,6 +152,7 @@ extern jmethodID JPy_Field_GetModifiers_MID; extern jmethodID JPy_Field_GetType_MID; extern jclass JPy_RuntimeException_JClass; +extern jclass JPy_OutOfMemoryError_JClass; extern jclass JPy_Boolean_JClass; extern jmethodID JPy_Boolean_Init_MID; From 71637e1a240d1e22e6cec4db1000bb1a60500881 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 10:06:57 -0400 Subject: [PATCH 12/42] Reran javah. --- src/main/c/jni/org_jpy_PyLib.h | 118 ++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 15 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index b9e8f87243..400055bb0d 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -49,12 +49,17 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript /* * Class: org_jpy_PyLib - * Method: execute + * Method: executeCode * Signature: (Ljava/lang/String;ILjava/util/Map;Ljava/util/Map;)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/util/Map;Ljava/util/Map;)J + */ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript (JNIEnv *, jclass, jstring, jint, jobject, jobject); @@ -82,8 +87,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* jenv, jclass jLibClass, jlong objId); + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: getDoubleValue @@ -108,37 +119,98 @@ 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: getObjectValue + * 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: 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); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck(JNIEnv *, jclass, jlong); -JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck(JNIEnv *, jclass, jlong); - /* * Class: org_jpy_PyLib - * Method: getObjectValue - * Signature: (J)Ljava/lang/Object; + * Method: str + * Signature: (J)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str (JNIEnv *, jclass, jlong); /* * Class: org_jpy_PyLib - * Method: getObjectValue - * Signature: (J)Ljava/lang/Object; + * Method: repr + * Signature: (J)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr (JNIEnv *, jclass, jlong); @@ -183,6 +255,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 From e05b35e75b9c2b73e07d4307d13105113d18d2a7 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 10:34:26 -0400 Subject: [PATCH 13/42] Comment cleanups. --- src/main/c/jni/org_jpy_PyLib.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index b7a33ec7e1..e37fe50977 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -348,7 +348,7 @@ void dumpDict(const char* dictName, PyObject* dict) } } -/* +/** * Calls PyRun_String under the covers to execute a python script using the __main__ globals. * * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to @@ -617,14 +617,9 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue return value; } -/* - * Class: org_jpy_python_PyLib - * Method: getBooleanValue - * Signature: (J)I - * +/** * 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) @@ -723,7 +718,7 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue 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. * @@ -748,11 +743,7 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getType - * Signature: (J)Lorg/jpy/PyObject; - * +/** * Gets the Python type object of specified objId. * * objId is a pointer to a PyObject. @@ -798,7 +789,7 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck } /** - * Evaluate PyDict_Check against a Python object. + * Evaluate PyList_Check against a Python object. * * @param jenv JNI environment. * @param jLibClass the PyLib class object @@ -1284,7 +1275,7 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue JPy_END_GIL_STATE } -/* +/** * Deletes an attribute from an object. * * @param jenv JNI environment. @@ -1722,8 +1713,12 @@ void PyLib_HandlePythonException(JNIEnv* jenv) PyErr_Clear(); } +/** + * Throw an OutOfMemoryError. + * @param jenv the jni environment + */ void PyLib_ThrowOOM(JNIEnv* jenv) { - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, "Out of memory"); + (*jenv)->ThrowNew(jenv, JPy_OutOfMemoryError_JClass, "Out of memory"); } //////////////////////////////////////////////////////////////////////////////////////////////// From 74a81a964851506672fec8caeb7baaa6154c6f5a Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 10:54:44 -0400 Subject: [PATCH 14/42] Variable initialization. --- src/main/c/jni/org_jpy_PyLib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index e37fe50977..140e9e8738 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -1468,8 +1468,8 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyObject, jstring jNa 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 = Py_None; const char* nameChars; From 4cd720885ed4f9ddb684f22a1e145332300b3dc3 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 14:04:10 -0400 Subject: [PATCH 15/42] Code review cleanups. --- src/main/c/jpy_module.c | 37 ++++++++++++++---------- src/main/java/org/jpy/PyDictWrapper.java | 4 +-- src/main/java/org/jpy/PyListWrapper.java | 2 +- src/main/java/org/jpy/PyObject.java | 15 ++++------ 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 6b6b191cf0..29e65fc9a4 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -986,7 +986,7 @@ void JPy_ClearGlobalVars(JNIEnv* jenv) #define AT_STRLEN 5 #define CAUSED_BY_STRING "caused by " #define CAUSED_BY_STRLEN 10 - +#define ELIDED_STRING_MAX_SIZE 30 void JPy_HandleJavaException(JNIEnv* jenv) { @@ -1001,7 +1001,7 @@ void JPy_HandleJavaException(JNIEnv* jenv) if (JPy_VerboseExceptions) { char *stackTraceString; - int stackTraceLength = 0; + size_t stackTraceLength = 0; stackTraceString = strdup(""); @@ -1015,6 +1015,11 @@ void JPy_HandleJavaException(JNIEnv* jenv) * default, as does the default printStackTrace(). */ jint ii; + jarray stackTrace; + jint stackTraceElements; + jint lastElementToPrint; + jint enclosingIndex; + if (stackTraceLength > 0) { char *newStackString; @@ -1057,11 +1062,10 @@ void JPy_HandleJavaException(JNIEnv* jenv) } /* We should assemble a string based on the stack trace. */ - jarray stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID); - - jint stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace); - jint lastElementToPrint = stackTraceElements - 1; - jint enclosingIndex = enclosingSize - 1; + 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); @@ -1082,17 +1086,19 @@ void JPy_HandleJavaException(JNIEnv* jenv) 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) { - (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); allocError = 1; break; } - size_t len = strlen(messageChars); + len = strlen(messageChars); - char *newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength); + newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength); if (newStackString == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); allocError = 1; break; } @@ -1111,17 +1117,18 @@ void JPy_HandleJavaException(JNIEnv* jenv) } if (lastElementToPrint < stackTraceElements - 1) { - char *newStackString = realloc(stackTraceString, stackTraceLength + 100); + int written; + char *newStackString = realloc(stackTraceString, stackTraceLength + ELIDED_STRING_MAX_SIZE); if (newStackString == NULL) { allocError = 1; break; } - stackTraceString[stackTraceLength + 100] = '\0'; + stackTraceString[stackTraceLength + ELIDED_STRING_MAX_SIZE - 1] = '\0'; stackTraceString = newStackString; - int written = snprintf(stackTraceString + stackTraceLength, 99, "\t... %d more\n", (stackTraceElements - lastElementToPrint) - 1); - if (written > 99) { - stackTraceLength += 99; + 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; } diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index a00c15f97a..1e4798f483 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -92,7 +92,7 @@ public boolean isEmpty() { @Override public boolean contains(Object o) { - return false; + throw new UnsupportedOperationException(); } @Override @@ -116,7 +116,7 @@ public boolean hasNext() { @Override public Entry next() { - PyObject oldNext = next; + final PyObject oldNext = next; prepareNext(); return new Entry() { diff --git a/src/main/java/org/jpy/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java index dbe6fcb947..3fd17cd42f 100644 --- a/src/main/java/org/jpy/PyListWrapper.java +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -66,7 +66,7 @@ public PyObject[] toArray() { public T[] toArray(T[] a) { int size = size(); - if (a.length < size()) { + if (a.length < size) { a = Arrays.copyOf(a, size); } for (int ii = 0; ii < size; ++ii) { diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index 1dcce572e2..357706d7e8 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -312,12 +312,9 @@ public void setAttribute(String name, T value) { } /** - * Sets the value of a Python attribute from the given Java object. - *

- * If the Java {@code value} cannot be directly converted into a Python object, a Java wrapper will be created instead. - * If the Java {@code value} is a wrapped Python object of type {@link PyObject}, it will be unwrapped. + * Deletes the value of a Python attribute. * - * @param name A name of the Python attribute. + * @param name the name of the Python attribute. */ public void delAttribute(String name) { assertPythonRuns(); @@ -325,12 +322,10 @@ public void delAttribute(String name) { } /** - * Sets the value of a Python attribute from the given Java object. - *

- * If the Java {@code value} cannot be directly converted into a Python object, a Java wrapper will be created instead. - * If the Java {@code value} is a wrapped Python object of type {@link PyObject}, it will be unwrapped. + * Checks for the existence of a Python attribute.. * - * @param name A name of the Python attribute. + * @param name the name of the Python attribute. + * @return whether this attribute exists for this object */ public boolean hasAttribute(String name) { assertPythonRuns(); From ba726cb22e75aaafea45a72becda763ff78c9dbe Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 15:04:52 -0400 Subject: [PATCH 16/42] Verbose exception test. --- src/main/c/jpy_module.c | 4 ++-- .../jpy/fixtures/ExceptionTestFixture.java | 22 ++++++++++++++----- src/test/python/jpy_exception_test.py | 19 +++++++++------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 29e65fc9a4..0e5339a289 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -982,8 +982,8 @@ void JPy_ClearGlobalVars(JNIEnv* jenv) JPy_JPyModule = NULL; } -#define AT_STRING "\t at " -#define AT_STRLEN 5 +#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 diff --git a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java index 43b531f702..4ac9e69f02 100644 --- a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java +++ b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java @@ -34,11 +34,23 @@ public int throwNpeIfArgIsNull2(String arg) { } public int throwNpeIfArgIsNullNested(String arg) { - try { - return throwNpeIfArgIsNull(arg); - } catch (Exception e) { - throw new RuntimeException("Nested exception", e); - } + 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) { diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index 1d87f72cf6..f8cef992e6 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -56,16 +56,19 @@ def test_VerboseException(self): jpy.VerboseExceptions.enabled = True - self.assertEqual(fixture.throwNpeIfArgIsNull("123456"), 6) + self.assertEqual(fixture.throwNpeIfArgIsNull("123456"), 6) - with self.assertRaises(RuntimeError) as e: + with self.assertRaises(RuntimeError) as e: fixture.throwNpeIfArgIsNullNested(None) - if False: - self.assertRegexpMatches(str(e.exception), """java.lang.RuntimeException: Nested exception - at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:40) -caused by java.lang.NullPointerException - at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29) - at org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n""") + actualMessage = str(e.exception) + expectedMessage = "java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:40)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n" + 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:52)\ncaused by java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:40)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested2(ExceptionTestFixture.java:45)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:50)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n\t... 2 more\n" + self.assertEquals(actualMessage, expectedMessage) jpy.VerboseExceptions.enabled = False From d1f3053d11dbfb3f338b8257d29daa55e097599c Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 15:19:32 -0400 Subject: [PATCH 17/42] Mark all files as modified, to comply with 'prominent notices stating we changed the files.' --- src/main/c/jni/org_jpy_PyLib.c | 3 +++ src/main/c/jpy_jmethod.c | 2 ++ src/main/c/jpy_jmethod.h | 2 ++ src/main/c/jpy_jobj.c | 3 +++ src/main/c/jpy_jobj.h | 3 +++ src/main/c/jpy_jtype.c | 3 +++ src/main/c/jpy_jtype.h | 4 ++++ src/main/c/jpy_module.c | 3 +++ src/main/c/jpy_module.h | 3 +++ src/main/c/jpy_verboseexcept.c | 3 +++ src/main/c/jpy_verboseexcept.h | 3 +++ src/main/java/org/jpy/PyDictWrapper.java | 18 ++++++++++++++++++ src/main/java/org/jpy/PyLib.java | 3 +++ src/main/java/org/jpy/PyListWrapper.java | 18 ++++++++++++++++++ src/main/java/org/jpy/PyObject.java | 3 +++ src/test/java/org/jpy/PyObjectTest.java | 3 +++ .../org/jpy/fixtures/ExceptionTestFixture.java | 3 +++ .../fixtures/TypeTranslationTestFixture.java | 3 +++ .../org/jpy/fixtures/VarArgsTestFixture.java | 3 +++ src/test/python/jpy_exception_test.py | 1 + src/test/python/jpy_overload_test.py | 1 + src/test/python/jpy_translation_test.py | 1 + 22 files changed, 89 insertions(+) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 140e9e8738..7d1fa8b456 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 diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 09ecb1e535..5015315dc1 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" diff --git a/src/main/c/jpy_jmethod.h b/src/main/c/jpy_jmethod.h index 961691f365..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 diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index 5841db9cfa..2d8acc2373 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" diff --git a/src/main/c/jpy_jobj.h b/src/main/c/jpy_jobj.h index e5104f18c8..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 diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 50411ee75e..8eb1a4dbb3 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" diff --git a/src/main/c/jpy_jtype.h b/src/main/c/jpy_jtype.h index 590c337f40..b1ee225001 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 diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 0e5339a289..7125ddecd4 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.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" diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index abcb093625..ea422bfab6 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 diff --git a/src/main/c/jpy_verboseexcept.c b/src/main/c/jpy_verboseexcept.c index 41a5d5435e..3c96fcf327 100644 --- a/src/main/c/jpy_verboseexcept.c +++ b/src/main/c/jpy_verboseexcept.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 diff --git a/src/main/c/jpy_verboseexcept.h b/src/main/c/jpy_verboseexcept.h index 3ab4a8a156..990972127b 100644 --- a/src/main/c/jpy_verboseexcept.h +++ b/src/main/c/jpy_verboseexcept.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_VERBOSEEXCEPT_H diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 1e4798f483..2958628139 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -1,3 +1,21 @@ +/* + * 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.*; diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index bc6e0be22d..76c68ef64a 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.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; diff --git a/src/main/java/org/jpy/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java index 3fd17cd42f..29068f7918 100644 --- a/src/main/java/org/jpy/PyListWrapper.java +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -1,3 +1,21 @@ +/* + * 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.*; diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index 357706d7e8..bf8ef9c27b 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.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; diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index ea6cb0dcf9..9812f9c367 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.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; diff --git a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java index 4ac9e69f02..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; diff --git a/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java index ae7b2b2eb1..f52b00919f 100644 --- a/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java +++ b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.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; diff --git a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java index 1aa8303f4d..162c53c1da 100644 --- a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.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; diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index f8cef992e6..79eb6357e4 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -1,3 +1,4 @@ +# This file was modified by Illumon. import unittest import jpyutil diff --git a/src/test/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index eb078a043c..24f6461eed 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 diff --git a/src/test/python/jpy_translation_test.py b/src/test/python/jpy_translation_test.py index 9fb095edbd..ca64cf47da 100644 --- a/src/test/python/jpy_translation_test.py +++ b/src/test/python/jpy_translation_test.py @@ -1,3 +1,4 @@ +# This file was modified by Illumon. import unittest import jpyutil From 2b40cc65d1880813ced41355f5a817ed4f05d4b3 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 28 Sep 2017 15:24:15 -0400 Subject: [PATCH 18/42] negative to not a positive. --- src/main/c/jpy_jtype.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 8eb1a4dbb3..05567af400 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1746,7 +1746,7 @@ int JType_ConvertVarArgPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* para 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; } @@ -2037,7 +2037,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; } From 9562da2668022912fb9ae62a95f96098bc582018 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 29 Sep 2017 11:06:56 -0400 Subject: [PATCH 19/42] Handle local and global maps for input to executeCode/executeScript. --- src/main/c/jni/org_jpy_PyLib.c | 429 +++++++++++++++++------- src/main/c/jpy_conv.c | 8 +- src/main/c/jpy_conv.h | 4 +- src/main/c/jpy_jtype.c | 18 +- src/main/c/jpy_jtype.h | 4 +- src/main/c/jpy_module.c | 48 ++- src/main/c/jpy_module.h | 17 + src/main/java/org/jpy/PyLib.java | 4 +- src/test/java/org/jpy/PyObjectTest.java | 28 +- 9 files changed, 411 insertions(+), 149 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 7d1fa8b456..287d968e4c 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -36,7 +36,13 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyValue, jstring jNam 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_ThrowUOE(JNIEnv* jenv, const char *message); void PyLib_RedirectStdOut(void); +int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap); + +static const char *repr(PyObject *po) { + return PyString_AsString(PyObject_Repr(po)); +} static int JPy_InitThreads = 0; @@ -331,7 +337,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) @@ -352,196 +358,358 @@ void dumpDict(const char* dictName, PyObject* dict) } /** - * Calls PyRun_String under the covers to execute a python script using the __main__ globals. - * - * 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. - * - * The jGLobals and jLocals parameters are ignored. + * Get the globals from the __main__ module. */ -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; +PyObject *getMainGlobals() { PyObject* pyMainModule; - int start; - - JPy_BEGIN_GIL_STATE - - pyGlobals = NULL; - pyLocals = NULL; - pyReturnValue = NULL; - pyMainModule = NULL; - codeChars = NULL; + PyObject* pyGlobals; pyMainModule = PyImport_AddModule("__main__"); // borrowed ref if (pyMainModule == NULL) { - PyLib_HandlePythonException(jenv); - goto error; + return NULL; } - codeChars = (*jenv)->GetStringUTFChars(jenv, jCode, NULL); - if (codeChars == NULL) { - PyLib_ThrowOOM(jenv); - goto error; + pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref + + return pyGlobals; +} + +/** + * 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; } - pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref - if (pyGlobals == NULL) { - PyLib_HandlePythonException(jenv); + entrySet = (*jenv)->CallObjectMethod(jenv, jMap, JPy_Map_entrySet_MID); + if (entrySet == NULL) { goto error; } - pyLocals = PyDict_New(); // new ref - if (pyLocals == NULL) { - PyLib_HandlePythonException(jenv); + iterator = (*jenv)->CallObjectMethod(jenv, entrySet, JPy_Set_Iterator_MID); + if (iterator == NULL) { 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) + hasNext = (*jenv)->CallBooleanMethod(jenv, iterator, JPy_Iterator_hasNext_MID); - start = jStart == JPy_IM_STATEMENT ? Py_single_input : - jStart == JPy_IM_SCRIPT ? Py_file_input : - Py_eval_input; + while (hasNext) { + jobject key, value; + char const *keyChars; + PyObject *pyKey; + PyObject *pyValue; + JPy_JType* type; - // by using the pyGlobals for the locals variable, we are able to execute Python code and - // retrieve values afterwards - // - // 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. - pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyGlobals); - if (pyReturnValue == NULL) { - PyLib_HandlePythonException(jenv); - goto error; + 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; + } + if (!(*jenv)->IsInstanceOf(jenv, key, JPy_String_JClass)) { + goto error; + } + + // we require string keys + keyChars = (*jenv)->GetStringUTFChars(jenv, (jstring)key, NULL); + if (keyChars == NULL) { + goto error; + } + + printf("Key: %s\n", keyChars); + + 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); } - // 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); + return result; error: - if (codeChars != NULL) { - (*jenv)->ReleaseStringUTFChars(jenv, jCode, codeChars); + if (result != NULL) { + Py_XDECREF(result); } + return NULL; +} - Py_XDECREF(pyLocals); +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; - JPy_END_GIL_STATE + if (!PyDict_Check(pyDict)) { + PyLib_ThrowUOE(jenv, "PyObject is not a dictionary!"); + return -1; + } - return (jlong) pyReturnValue; + dictSize = PyDict_Size(pyDict); + printf("Doing copy back of dictionary: %zd\n", dictSize); + + 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; + } + printf("Converting: %s\n", repr(pyValue)); + if (JPy_AsJObject(jenv, pyValue, &(jValues[ii]), JNI_TRUE) < 0) { + printf("Value Conversion Error!\n"); + // 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 + (*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*); + /** - * Calls PyRun_File under the covers to execute a python script using the __main__ globals. + * 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. * - * The jGLobals and jLocals parameters are 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) -{ - FILE *fp; - const char* fileChars; - PyObject* pyReturnValue; - PyObject* pyGlobals; - PyObject* pyLocals; - PyObject* pyMainModule; +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 - fp = NULL; + decGlobals = decLocals = JNI_FALSE; + copyGlobals = copyLocals = JNI_FALSE; pyGlobals = NULL; pyLocals = NULL; pyReturnValue = NULL; - pyMainModule = NULL; - fileChars = NULL; - - pyMainModule = PyImport_AddModule("__main__"); // borrowed ref - if (pyMainModule == NULL) { - PyLib_HandlePythonException(jenv); - goto error; - } - fileChars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); - if (fileChars == NULL) { - PyLib_ThrowOOM(jenv); - goto error; - } - - fp = fopen(fileChars, "r"); - if (!fp) { - goto error; - } - - pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref - if (pyGlobals == NULL) { - PyLib_HandlePythonException(jenv); + 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_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 + copyGlobals = decGlobals = JNI_TRUE; + pyGlobals = copyJavaStringObjectMapToPyDict(jenv, jGlobals); + } else { + PyLib_ThrowUOE(jenv, "Unsupported globals type"); goto error; } - pyLocals = PyDict_New(); // new ref - if (pyLocals == NULL) { - PyLib_HandlePythonException(jenv); + // 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_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 + copyLocals = decLocals = JNI_TRUE; + pyLocals = copyJavaStringObjectMapToPyDict(jenv, jLocals); + } else { + PyLib_ThrowUOE(jenv, "Unsupported locals type"); 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) - start = jStart == JPy_IM_STATEMENT ? Py_single_input : jStart == JPy_IM_SCRIPT ? Py_file_input : Py_eval_input; - // by using the pyGlobals for the locals variable, we are able to execute Python code and - // retrieve values afterwards - // - // 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. - pyReturnValue = PyRun_File(fp, fileChars, start, pyGlobals, pyGlobals); + 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; } - // 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); - error: - if (fileChars != NULL) { - (*jenv)->ReleaseStringUTFChars(jenv, jFile, fileChars); + 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 (fp != NULL) { - fclose(fp); + if (decGlobals) { + Py_XDECREF(pyGlobals); + } + if (decLocals) { + Py_XDECREF(pyLocals); } - Py_XDECREF(pyLocals); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: return value %s\n", repr(pyReturnValue)); 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); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Result in wrapper: %s\n", repr(result)); + return result; +} + +/** + * Calls PyRun_String under the covers to execute a python script using the __main__ globals. + * + * 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) { + 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); +} + +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) { + RunFileArgs runFileArgs; + jlong result; + + runFileArgs.fp = NULL; + runFileArgs.filechars = NULL; + + runFileArgs.filechars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); + if (runFileArgs.filechars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + + runFileArgs.fp = fopen(runFileArgs.filechars, "r"); + if (!runFileArgs.fp) { + goto error; + } + + 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 @@ -709,7 +877,7 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue if (JObj_Check(pyObject)) { jObject = ((JPy_JObj*) pyObject)->objectRef; } else { - if (JPy_AsJObject(jenv, pyObject, &jObject) < 0) { + 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); @@ -1097,7 +1265,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"); @@ -1724,6 +1892,15 @@ void PyLib_ThrowOOM(JNIEnv* jenv) { (*jenv)->ThrowNew(jenv, JPy_OutOfMemoryError_JClass, "Out of memory"); } +/** + * 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); +} + //////////////////////////////////////////////////////////////////////////////////////////////// // Redirect stdout 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_jtype.c b/src/main/c/jpy_jtype.c index 05567af400..e912df3b17 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -448,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; @@ -722,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; @@ -745,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. @@ -761,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) { @@ -791,6 +793,8 @@ int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyA return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); } else if (JPy_IS_STR(pyArg)) { return JPy_AsJString(jenv, pyArg, objectRef); + } else if (allowObjectWrapping) { + return JType_CreateJavaPyObject(jenv, JPy_JPyObject, pyArg, objectRef); } } else if (type == JPy_JString) { if (JPy_IS_STR(pyArg)) { @@ -1818,7 +1822,7 @@ int JType_ConvertVarArgPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* para 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; @@ -2109,7 +2113,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; diff --git a/src/main/c/jpy_jtype.h b/src/main/c/jpy_jtype.h index b1ee225001..83b94df54a 100644 --- a/src/main/c/jpy_jtype.h +++ b/src/main/c/jpy_jtype.h @@ -123,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 7125ddecd4..4992db4cb7 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -168,8 +168,25 @@ 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; // java.lang.Boolean jclass JPy_Boolean_JClass = NULL; @@ -206,6 +223,7 @@ jmethodID JPy_Number_DoubleValue_MID = NULL; jclass JPy_Void_JClass = NULL; jclass JPy_String_JClass = NULL; +jclass JPy_PyObject_JClass = NULL; jmethodID JPy_PyObject_GetPointer_MID = NULL; jmethodID JPy_PyObject_Init_MID = NULL; @@ -613,12 +631,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; @@ -729,8 +747,9 @@ int initGlobalPyObjectVars(JNIEnv* jenv) 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); @@ -783,8 +802,27 @@ 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_UnsupportedOperationException_JClass, "java/lang/UnsupportedOperationException"); DEFINE_CLASS(JPy_Boolean_JClass, "java/lang/Boolean"); DEFINE_METHOD(JPy_Boolean_Init_MID, JPy_Boolean_JClass, "", "(Z)V"); diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index ea422bfab6..7030a7c99d 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -153,9 +153,25 @@ 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_UnsupportedOperationException_JClass; extern jclass JPy_Boolean_JClass; extern jmethodID JPy_Boolean_Init_MID; @@ -191,6 +207,7 @@ 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; diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index 76c68ef64a..cc63996487 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -235,10 +235,10 @@ 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, Map globals, Map locals); + (String file, int start, Object globals, Object locals); static native void incRef(long pointer); diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index 9812f9c367..c191b5e458 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -26,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.*; @@ -131,7 +132,6 @@ public void testExecuteCode_Script() throws Exception { assertNotNull(pyVoid); assertEquals(null, pyVoid.getObjectValue()); -/* assertNotNull(localMap.get("jpy")); assertNotNull(localMap.get("File")); assertNotNull(localMap.get("f")); @@ -140,7 +140,31 @@ public void testExecuteCode_Script() throws Exception { 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 From 846237eb00fa8af606b4c91452a59e46915d3398 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 29 Sep 2017 12:31:52 -0400 Subject: [PATCH 20/42] Access to main dictionary and copy. --- src/main/c/jni/org_jpy_PyLib.c | 56 +++++++++++++++++++++++- src/main/c/jni/org_jpy_PyLib.h | 36 ++++++++++++++- src/main/java/org/jpy/PyDictWrapper.java | 8 ++++ src/main/java/org/jpy/PyLib.java | 7 +++ src/main/java/org/jpy/PyObject.java | 9 +++- src/test/java/org/jpy/PyLibTest.java | 35 +++++++++++++++ src/test/java/org/jpy/PyObjectTest.java | 26 +++++++++++ src/test/python/jpy_exception_test.py | 18 +++++++- 8 files changed, 187 insertions(+), 8 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 287d968e4c..cd93c035e2 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -364,16 +364,70 @@ 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. */ @@ -425,8 +479,6 @@ PyObject *copyJavaStringObjectMapToPyDict(JNIEnv *jenv, jobject jMap) { goto error; } - printf("Key: %s\n", keyChars); - pyKey = JPy_FROM_CSTR(keyChars); (*jenv)->ReleaseStringUTFChars(jenv, (jstring)key, keyChars); diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index 400055bb0d..e4b2a3dfb0 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -50,7 +50,7 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript /* * Class: org_jpy_PyLib * Method: executeCode - * Signature: (Ljava/lang/String;ILjava/util/Map;Ljava/util/Map;)J + * 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); @@ -58,11 +58,27 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode /* * Class: org_jpy_PyLib * Method: executeScript - * Signature: (Ljava/lang/String;ILjava/util/Map;Ljava/util/Map;)J + * 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 @@ -183,6 +199,14 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck 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 @@ -215,6 +239,14 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str 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 diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 2958628139..2f426943f3 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -97,6 +97,14 @@ public Set> entrySet() { return new EntrySet(); } + PyObject unwrap() { + return pyObject; + } + + PyDictWrapper copy() { + return new PyDictWrapper(PyLib.copyDict(pyObject.getPointer())); + } + private class EntrySet implements Set> { @Override public int size() { diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index cc63996487..045051be66 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -240,6 +240,10 @@ public static void stopPython() { static native long executeScript (String file, int start, Object globals, Object locals); + static native PyObject getMainGlobals(); + + static native PyObject copyDict(long pyPointer); + static native void incRef(long pointer); static native void decRef(long pointer); @@ -262,6 +266,7 @@ public static void stopPython() { 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); @@ -270,6 +275,8 @@ public static void stopPython() { static native String repr(long pointer); + static native PyObject newDict(); + static native T[] getObjectArrayValue(long pointer, Class itemType); static native long importModule(String name); diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index bf8ef9c27b..2e7913a05a 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -81,7 +81,7 @@ public static PyObject executeScript(String script, PyInputMode mode) { * @param locals The locals variables to be set. * @return The result of executing the code as a Python object. */ - public static PyObject executeCode(String code, PyInputMode mode, Map globals, Map locals) { + public static PyObject executeCode(String code, PyInputMode mode, Object globals, Object locals) { if (code == null) { throw new NullPointerException("code must not be null"); } @@ -231,6 +231,11 @@ public boolean isCallable() { return PyLib.pyCallableCheck(getPointer()); } + public boolean isString() { + assertPythonRuns(); + return PyLib.pyStringCheck(getPointer()); + } + public boolean isConvertible() { assertPythonRuns(); return PyLib.isConvertible(getPointer()); @@ -243,7 +248,7 @@ public List asList() { return new PyListWrapper(this); } - public Map asDict() { + public PyDictWrapper asDict() { if (!isDict()) { throw new ClassCastException("Can not convert non-list type to a dictionary!"); } 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 c191b5e458..c355c8a7ab 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -216,6 +216,32 @@ 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(); diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index 79eb6357e4..ffa3060f97 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -2,6 +2,7 @@ import unittest import jpyutil +#import string jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) import jpy @@ -52,6 +53,13 @@ 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() @@ -62,13 +70,19 @@ def test_VerboseException(self): 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:40)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n" + 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.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:52)\ncaused by java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:40)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested2(ExceptionTestFixture.java:45)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:50)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:29)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:38)\n\t... 2 more\n" + 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 From ad7f778adb0b07b5d5e6982a46b45a53e5c6a4fe Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 29 Sep 2017 14:28:25 -0400 Subject: [PATCH 21/42] Typed exceptions, use dictionary wrappers as globals. --- src/main/c/jni/org_jpy_PyLib.c | 23 +++++++-- src/main/c/jpy_module.c | 31 ++++++++++++ src/main/c/jpy_module.h | 5 ++ src/main/java/org/jpy/KeyError.java | 28 +++++++++++ src/main/java/org/jpy/PyDictWrapper.java | 49 ++++++++++++++++--- src/main/java/org/jpy/PyLib.java | 2 +- src/main/java/org/jpy/PyObject.java | 2 +- .../org/jpy/annotations/StopIteration.java | 28 +++++++++++ 8 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/jpy/KeyError.java create mode 100644 src/main/java/org/jpy/annotations/StopIteration.java diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index cd93c035e2..284f4b4631 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -613,6 +613,10 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob // 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 PyObject, just use the object + 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 @@ -635,6 +639,10 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob // 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, jGlobals, JPy_PyDictWrapper_JClass)) { + // if we are an instance of PyObject, just use the object + 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 @@ -1860,6 +1868,8 @@ void PyLib_HandlePythonException(JNIEnv* jenv) char* filenameChars = NULL; char* namespaceChars = NULL; + jclass jExceptionClass; + if (PyErr_Occurred() == NULL) { return; } @@ -1873,6 +1883,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; @@ -1915,13 +1932,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); diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 4992db4cb7..52e0b7b64c 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -187,6 +187,8 @@ jmethodID JPy_Iterator_hasNext_MID = NULL; jclass JPy_RuntimeException_JClass = NULL; jclass JPy_OutOfMemoryError_JClass = NULL; jclass JPy_UnsupportedOperationException_JClass = NULL; +jclass JPy_KeyError_JClass = NULL; +jclass JPy_StopIteration_JClass = NULL; // java.lang.Boolean jclass JPy_Boolean_JClass = NULL; @@ -224,11 +226,14 @@ 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; @@ -758,6 +763,32 @@ int initGlobalPyObjectVars(JNIEnv* jenv) PyErr_Clear(); return -1; } + + JPy_JType *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"); + } + + JPy_JType *keyErrorType = JType_GetTypeForName(jenv, "org.jpy.KeyError", JNI_FALSE); + if (keyErrorType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_KeyError_JClass = keyErrorType->classRef; + } + + JPy_JType *stopIterationType = JType_GetTypeForName(jenv, "org.jpy.StopIteration", JNI_FALSE); + if (stopIterationType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_StopIteration_JClass = stopIterationType->classRef; + } + return 0; } diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index 7030a7c99d..06484951e0 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -172,6 +172,8 @@ extern jmethodID JPy_Iterator_hasNext_MID; extern jclass JPy_RuntimeException_JClass; extern jclass JPy_OutOfMemoryError_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; @@ -211,6 +213,9 @@ 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/java/org/jpy/KeyError.java b/src/main/java/org/jpy/KeyError.java new file mode 100644 index 0000000000..7008674610 --- /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 programtically 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 index 2f426943f3..88f907fe51 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -46,6 +46,10 @@ public boolean containsKey(Object key) { return pyObject.callMethod("has_key", key).getBooleanValue(); } + public boolean containsKey(String key) { + return pyObject.callMethod("has_key", key).getBooleanValue(); + } + @Override public boolean containsValue(Object value) { return pyObject.callMethod("values").asList().contains(value); @@ -61,14 +65,28 @@ public PyObject put(PyObject key, PyObject value) { return pyObject.callMethod("__setitem__", key, value); } + public PyObject putObject(Object key, Object value) { + return pyObject.callMethod("__setitem__", key, value); + } + @Override public PyObject remove(Object key) { - PyObject value = get(key); - if (value.isNone()) { + try { + PyObject value = get(key); + pyObject.callMethod("__delitem__", key); + return value; + } catch (KeyError ke) { return null; - } else { + } + } + + public PyObject remove(String key) { + try { + PyObject value = get(key); pyObject.callMethod("__delitem__", key); return value; + } catch (KeyError ke) { + return null; } } @@ -97,11 +115,30 @@ public Set> entrySet() { return new EntrySet(); } - PyObject unwrap() { + /** + * Gets the underlying PyObject. + * + * @return the PyObject wrapped by this dictionary. + */ + public PyObject unwrap() { return pyObject; } - PyDictWrapper copy() { + /** + * Gets the underlying PyObject. + * + * @return the PyObject 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())); } @@ -130,7 +167,7 @@ public Iterator> iterator() { private PyObject prepareNext() { try { return next = it.callMethod("next"); - } catch (Exception e) { + } catch (Stopteration e) { return next = null; } } diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index 045051be66..373f6ccc88 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -240,7 +240,7 @@ public static void stopPython() { static native long executeScript (String file, int start, Object globals, Object locals); - static native PyObject getMainGlobals(); + public static native PyObject getMainGlobals(); static native PyObject copyDict(long pyPointer); diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index 2e7913a05a..1630b0b0b1 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -104,7 +104,7 @@ public static PyObject executeCode(String code, PyInputMode mode, Object globals * @param locals The locals variables to be set. * @return The result of executing the script as a Python object. */ - public static PyObject executeScript(String script, PyInputMode mode, Map globals, Map locals) { + public static PyObject executeScript(String script, PyInputMode mode, Object globals, Object locals) { if (script == null) { throw new NullPointerException("script must not be null"); } diff --git a/src/main/java/org/jpy/annotations/StopIteration.java b/src/main/java/org/jpy/annotations/StopIteration.java new file mode 100644 index 0000000000..ed1c74b777 --- /dev/null +++ b/src/main/java/org/jpy/annotations/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 KeyErrors so that they can be programtically detected from Java. + */ +public class StopIteration extends RuntimeException { + StopIteration(String message) { + super(message); + } +} \ No newline at end of file From 4c23a75af369960d9788f5c9c07cc504f653fd2c Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 29 Sep 2017 15:12:59 -0400 Subject: [PATCH 22/42] Fix memory error in verbose exceptions. --- src/main/c/jpy_module.c | 3 ++- src/main/java/org/jpy/PyDictWrapper.java | 2 +- src/test/python/jpy_exception_test.py | 18 +++++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index 52e0b7b64c..e0c6674980 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -1195,9 +1195,10 @@ void JPy_HandleJavaException(JNIEnv* jenv) allocError = 1; break; } - stackTraceString[stackTraceLength + ELIDED_STRING_MAX_SIZE - 1] = '\0'; 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); diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 88f907fe51..a02144a11f 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -167,7 +167,7 @@ public Iterator> iterator() { private PyObject prepareNext() { try { return next = it.callMethod("next"); - } catch (Stopteration e) { + } catch (StopIteration e) { return next = null; } } diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index ffa3060f97..3888aeee0c 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -2,7 +2,7 @@ import unittest import jpyutil -#import string +import string jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) import jpy @@ -54,11 +54,11 @@ def test_IOException(self): 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 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() @@ -72,6 +72,10 @@ def test_VerboseException(self): 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: @@ -81,7 +85,7 @@ def test_VerboseException(self): #self.hexdump(actualMessage) #self.hexdump(expectedMessage) - #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] + #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] self.assertEquals(actualMessage, expectedMessage) From 0994e000c0bc89122155925100dadf65773fe881 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 29 Sep 2017 15:27:03 -0400 Subject: [PATCH 23/42] Fix copy and paste error. --- src/main/c/jni/org_jpy_PyLib.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 284f4b4631..bc0363c1cb 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -347,6 +347,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)) { @@ -639,7 +644,7 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob // 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, jGlobals, JPy_PyDictWrapper_JClass)) { + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_PyDictWrapper_JClass)) { // if we are an instance of PyObject, just use the object 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"); From 447db6a997e8c9b66965e875f97dfc22c5378e7b Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Sat, 30 Sep 2017 13:12:36 -0400 Subject: [PATCH 24/42] FileNotFoundException when a script issn't found. --- src/main/c/jni/org_jpy_PyLib.c | 14 ++++++++++++-- src/main/c/jpy_module.c | 2 ++ src/main/c/jpy_module.h | 1 + src/main/java/org/jpy/PyDictWrapper.java | 4 ++++ src/main/java/org/jpy/PyLib.java | 3 ++- src/main/java/org/jpy/PyObject.java | 6 ++++-- 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index bc0363c1cb..d6814b3f73 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -36,6 +36,7 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyValue, jstring jNam 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_RedirectStdOut(void); int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap); @@ -747,7 +748,7 @@ PyObject *pyRunFileWrapper(RunFileArgs *args, int start, PyObject *globals, PyOb JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) { RunFileArgs runFileArgs; - jlong result; + jlong result = 0; runFileArgs.fp = NULL; runFileArgs.filechars = NULL; @@ -760,12 +761,13 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript runFileArgs.fp = fopen(runFileArgs.filechars, "r"); if (!runFileArgs.fp) { + PyLib_ThrowFNFE(jenv, runFileArgs.filechars); goto error; } result = executeInternal(jenv, jLibClass, jStart, jGlobals, jLocals, (DoRun)pyRunFileWrapper, &runFileArgs); - error: +error: if (runFileArgs.filechars != NULL) { (*jenv)->ReleaseStringUTFChars(jenv, jFile, runFileArgs.filechars); } @@ -1966,6 +1968,14 @@ 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 diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index e0c6674980..ffb9c7b553 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -187,6 +187,7 @@ 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; @@ -853,6 +854,7 @@ int JPy_InitGlobalVars(JNIEnv* jenv) 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"); diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index 06484951e0..7070da2b6c 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -171,6 +171,7 @@ 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; diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index a02144a11f..97c035860c 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -60,6 +60,10 @@ public PyObject get(Object key) { return pyObject.callMethod("__getitem__", key); } + public PyObject get(String key) { + return pyObject.callMethod("__getitem__", key); + } + @Override public PyObject put(PyObject key, PyObject value) { return pyObject.callMethod("__setitem__", key, value); diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index 373f6ccc88..903e268cc6 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -20,6 +20,7 @@ package org.jpy; import java.io.File; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Map; @@ -238,7 +239,7 @@ public static void stopPython() { static native long executeCode(String code, int start, Object globals, Object locals); static native long executeScript - (String file, int start, Object globals, Object locals); + (String file, int start, Object globals, Object locals) throws FileNotFoundException; public static native PyObject getMainGlobals(); diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index 1630b0b0b1..0eea6b4aec 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -23,6 +23,7 @@ import java.lang.reflect.Proxy; import java.util.List; import java.util.Map; +import java.io.FileNotFoundException; import static org.jpy.PyLib.assertPythonRuns; @@ -65,7 +66,7 @@ public static PyObject executeCode(String code, PyInputMode mode) { * @param mode The execution mode. * @return The result of executing the script as a Python object. */ - public static PyObject executeScript(String script, PyInputMode mode) { + public static PyObject executeScript(String script, PyInputMode mode) throws FileNotFoundException { return executeScript(script, mode, null, null); } @@ -103,8 +104,9 @@ public static PyObject executeCode(String code, PyInputMode mode, Object globals * @param globals The global variables to be set. * @param locals The locals variables to be set. * @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) { + public static PyObject executeScript(String script, PyInputMode mode, Object globals, Object locals) throws FileNotFoundException { if (script == null) { throw new NullPointerException("script must not be null"); } From 53c4fdf4e6d7a878968055b77b903aef560d1f98 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Sat, 30 Sep 2017 15:21:44 -0400 Subject: [PATCH 25/42] Type matching for String lists. --- src/main/c/jpy_jtype.c | 17 +++++++++++++++++ .../org/jpy/fixtures/VarArgsTestFixture.java | 11 +++++++++-- src/test/python/jpy_overload_test.py | 6 ++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index e912df3b17..6115c35757 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1962,6 +1962,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 (!PyString_Check(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) { diff --git a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java index 162c53c1da..230fdf2008 100644 --- a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java @@ -67,11 +67,18 @@ public String joinObjects(String prefix, Object ... a) { } public int chooseFixedArity(int... a) { - return 2; + return 2; } public int chooseFixedArity() { - return 1; + 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) { diff --git a/src/test/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 24f6461eed..b2d2334abe 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -126,6 +126,12 @@ def test_fixedArity(self): 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): From 13275843ac500690564632289b90bfc29df233d5 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 10:01:16 -0400 Subject: [PATCH 26/42] Remove debug prints. --- src/main/c/jni/org_jpy_PyLib.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index d6814b3f73..ab7fb91fae 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -41,10 +41,6 @@ void PyLib_ThrowUOE(JNIEnv* jenv, const char *message); void PyLib_RedirectStdOut(void); int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap); -static const char *repr(PyObject *po) { - return PyString_AsString(PyObject_Repr(po)); -} - static int JPy_InitThreads = 0; //#define JPy_JNI_DEBUG 1 @@ -524,7 +520,6 @@ int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap) { } dictSize = PyDict_Size(pyDict); - printf("Doing copy back of dictionary: %zd\n", dictSize); jKeys = malloc(dictSize * sizeof(jobject)); jValues = malloc(dictSize * sizeof(jobject)); @@ -547,9 +542,7 @@ int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap) { // an error occurred goto error; } - printf("Converting: %s\n", repr(pyValue)); if (JPy_AsJObject(jenv, pyValue, &(jValues[ii]), JNI_TRUE) < 0) { - printf("Value Conversion Error!\n"); // an error occurred goto error; } @@ -686,8 +679,6 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob Py_XDECREF(pyLocals); } - JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: return value %s\n", repr(pyReturnValue)); - JPy_END_GIL_STATE return (jlong) pyReturnValue; From 52a3991b69990d1571c37fd738f42a568fcbc73f Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 12:25:10 -0400 Subject: [PATCH 27/42] Check for NULL from map to dict conversion. Remove REPR printf. --- src/main/c/jni/org_jpy_PyLib.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index ab7fb91fae..4f69cef6af 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -38,6 +38,7 @@ 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); @@ -619,8 +620,12 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob } 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 - copyGlobals = decGlobals = JNI_TRUE; 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; @@ -645,8 +650,12 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob } 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 - copyLocals = decLocals = JNI_TRUE; 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; @@ -686,7 +695,6 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob PyObject *pyRunStringWrapper(const char *code, int start, PyObject *globals, PyObject *locals) { PyObject *result = PyRun_String(code, start, globals, locals); - JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Result in wrapper: %s\n", repr(result)); return result; } @@ -1976,6 +1984,15 @@ 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 From 40563fd4c94c44a0b9023c01e0da149e18d2d4fe Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 12:27:54 -0400 Subject: [PATCH 28/42] Move comment. --- src/main/c/jni/org_jpy_PyLib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 4f69cef6af..38d6e52dd9 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -472,11 +472,11 @@ PyObject *copyJavaStringObjectMapToPyDict(JNIEnv *jenv, jobject jMap) { if (key == NULL) { goto error; } + + // we require string keys if (!(*jenv)->IsInstanceOf(jenv, key, JPy_String_JClass)) { goto error; } - - // we require string keys keyChars = (*jenv)->GetStringUTFChars(jenv, (jstring)key, NULL); if (keyChars == NULL) { goto error; From ccd6478b4da74b44bff1416634bc7d33db79857d Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 12:30:50 -0400 Subject: [PATCH 29/42] Fix comment. --- src/main/c/jni/org_jpy_PyLib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 38d6e52dd9..de6cfe94c1 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -553,7 +553,7 @@ int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap) { // 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 + // 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! From 49e892ae7207be39481a3a24edd1f3467bb56b1f Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 12:33:08 -0400 Subject: [PATCH 30/42] Fix comment. --- src/main/c/jni/org_jpy_PyLib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index de6cfe94c1..a8ff4fa071 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -614,7 +614,7 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob 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 PyObject, just use the object + // 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)) { @@ -644,7 +644,7 @@ jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlob 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 PyObject, just use the object + // 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)) { From 915b3061460adeb58cb3f7d290207c1b55974c11 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 12:41:34 -0400 Subject: [PATCH 31/42] Comment fixes. --- src/main/c/jni/org_jpy_PyLib.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index a8ff4fa071..a371451ca9 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -699,7 +699,7 @@ PyObject *pyRunStringWrapper(const char *code, int start, PyObject *globals, PyO } /** - * Calls PyRun_String under the covers to execute a python script using the __main__ globals. + * 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. @@ -744,6 +744,20 @@ PyObject *pyRunFileWrapper(RunFileArgs *args, int start, PyObject *globals, PyOb 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; From 24704ea55c27c05fad3978f956e1d9b8095925c1 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 13:06:43 -0400 Subject: [PATCH 32/42] Comment fix. --- src/main/java/org/jpy/PyDictWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 97c035860c..4e5c50f9ce 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -129,9 +129,9 @@ public PyObject unwrap() { } /** - * Gets the underlying PyObject. + * Gets the underlying pointer for this object. * - * @return the PyObject wrapped by this dictionary. + * @return the pointer to the underlying Python object wrapped by this dictionary. */ long getPointer() { return pyObject.getPointer(); From 6b486356a0b4b5a41b8e584128139836f090fc7a Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 13:10:33 -0400 Subject: [PATCH 33/42] Comment fix. --- src/main/java/org/jpy/KeyError.java | 2 +- src/main/java/org/jpy/annotations/StopIteration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jpy/KeyError.java b/src/main/java/org/jpy/KeyError.java index 7008674610..c3a44c5604 100644 --- a/src/main/java/org/jpy/KeyError.java +++ b/src/main/java/org/jpy/KeyError.java @@ -19,7 +19,7 @@ package org.jpy; /** - * Translation of Python KeyErrors so that they can be programtically detected from Java. + * Translation of Python KeyErrors so that they can be programmatically detected from Java. */ public class KeyError extends RuntimeException { KeyError(String message) { diff --git a/src/main/java/org/jpy/annotations/StopIteration.java b/src/main/java/org/jpy/annotations/StopIteration.java index ed1c74b777..ed379066a4 100644 --- a/src/main/java/org/jpy/annotations/StopIteration.java +++ b/src/main/java/org/jpy/annotations/StopIteration.java @@ -19,7 +19,7 @@ package org.jpy; /** - * Translation of Python KeyErrors so that they can be programtically detected from Java. + * Translation of Python StopIteration so that they can be programmatically detected from Java. */ public class StopIteration extends RuntimeException { StopIteration(String message) { From aa02e8c7f56837b8160fbcb2dcb4e3602dff9ff5 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 2 Oct 2017 13:26:05 -0400 Subject: [PATCH 34/42] PyDictWrapper: Use delegation for String/Object overloads. --- src/main/java/org/jpy/PyDictWrapper.java | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index 4e5c50f9ce..b2be9247af 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -46,8 +46,11 @@ 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 pyObject.callMethod("has_key", key).getBooleanValue(); + return containsKey((Object)key); } @Override @@ -60,15 +63,21 @@ 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 pyObject.callMethod("__setitem__", key, 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); } @@ -85,13 +94,7 @@ public PyObject remove(Object key) { } public PyObject remove(String key) { - try { - PyObject value = get(key); - pyObject.callMethod("__delitem__", key); - return value; - } catch (KeyError ke) { - return null; - } + return remove((Object)key); } @Override From 445d1ad9c5b16e6dc757383830de078a4b58b837 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Fri, 6 Oct 2017 02:29:13 +0000 Subject: [PATCH 35/42] jpy_jmethod.c: move variable out of loop initializer. --- src/main/c/jpy_jmethod.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 5015315dc1..3e6e05cc5d 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -882,9 +882,10 @@ int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMe 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 (Py_ssize_t ii = 0; ii < size; ii++) { + 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 From 5adfb4afb48d572982c4c5184192a53362780cd7 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Mon, 11 Dec 2017 09:00:00 -0500 Subject: [PATCH 36/42] Method overloading should check assignability for String/primitives. When we have a String or primitive (e.g. int) Python argument; we should check for object assignability to the argument type. For example, a Python string, long, or float should be an acceptable value for a java.lang.Comparable. --- src/main/c/jpy_jtype.c | 57 +++++++++++++------ .../fixtures/MethodOverloadTestFixture.java | 10 ++++ src/test/python/jpy_overload_test.py | 20 +++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 6115c35757..5a6d5b8c7e 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -784,22 +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 (allowObjectWrapping) { - return JType_CreateJavaPyObject(jenv, JPy_JPyObject, 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); } @@ -2009,6 +2007,33 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr } else if (PyBool_Check(pyArg)) { return 10; } + } else { + if (PyString_Check(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; 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/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index b2d2334abe..5b111b9781 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -85,6 +85,26 @@ 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') From 6bac924031db4fc71144f428709ab5f1447498b3 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Thu, 21 Dec 2017 13:56:07 -0500 Subject: [PATCH 37/42] jpy_jtype.c: Exclude bridge methods to prevent covariant return problems. --- src/main/c/jpy_jtype.c | 5 ++- .../CovariantOverloadExtendTestFixture.java | 38 ++++++++++++++++ .../CovariantOverloadTestFixture.java | 44 +++++++++++++++++++ src/test/python/jpy_overload_test.py | 12 ++++- 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java create mode 100644 src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 5a6d5b8c7e..93e7eeb123 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1080,6 +1080,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) jboolean isStatic; jboolean isVarArg; jboolean isPublic; + jboolean isBridge; const char* methodName; jmethodID mid; PyObject* methodKey; @@ -1098,7 +1099,9 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) isPublic = (modifiers & 0x0001) != 0; isStatic = (modifiers & 0x0008) != 0; isVarArg = (modifiers & 0x0080) != 0; - if (isPublic) { + 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); 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/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 5b111b9781..b9be7a1d72 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -203,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() From 9ab5d6eb5e35f4980e098f0aac925b9743dce127 Mon Sep 17 00:00:00 2001 From: Darin Petty Date: Fri, 27 Apr 2018 15:53:46 -0600 Subject: [PATCH 38/42] move declarations to be C89 compatible (for Windows) --- src/main/c/jni/org_jpy_PyLib.c | 6 +++-- src/main/c/jpy_jobj.c | 3 +-- src/main/c/jpy_jtype.c | 45 +++++++++++++++++++--------------- src/main/c/jpy_module.c | 16 ++++++------ 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index a371451ca9..a07f44a535 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -1259,12 +1259,13 @@ 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; - PyObject *pyStr = PyObject_Str(pyObject); + pyStr = PyObject_Str(pyObject); if (pyStr) { jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); Py_DECREF(pyStr); @@ -1291,12 +1292,13 @@ 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 *pyStr = PyObject_Repr(pyObject); + pyStr = PyObject_Repr(pyObject); if (pyStr) { jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); Py_DECREF(pyStr); diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index 2d8acc2373..ab1bd6ca6b 100644 --- a/src/main/c/jpy_jobj.c +++ b/src/main/c/jpy_jobj.c @@ -99,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) @@ -121,8 +122,6 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) return -1; } - int isVarArgsArray; - jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE, &isVarArgsArray); if (jMethod == NULL) { return -1; diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 93e7eeb123..88cf11f831 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1516,6 +1516,10 @@ int JType_MatchVarArgPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* para Py_ssize_t remaining = (argCount - idx); JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + if (componentType == NULL) { return 0; } @@ -1524,10 +1528,7 @@ int JType_MatchVarArgPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* para return 10; } - PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); - - int minMatch = 100; - int ii; + 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); @@ -1545,6 +1546,10 @@ int JType_MatchVarArgPyArgAsJStringParam(JNIEnv* jenv, JPy_ParamDescriptor* para 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; } @@ -1553,10 +1558,7 @@ int JType_MatchVarArgPyArgAsJStringParam(JNIEnv* jenv, JPy_ParamDescriptor* para return 10; } - PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); - - int minMatch = 100; - int ii; + 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); @@ -1574,6 +1576,10 @@ int JType_MatchVarArgPyArgAsJBooleanParam(JNIEnv *jenv, JPy_ParamDescriptor *par 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; @@ -1583,10 +1589,7 @@ int JType_MatchVarArgPyArgAsJBooleanParam(JNIEnv *jenv, JPy_ParamDescriptor *par return 10; } - PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); - - int minMatch = 100; - int ii; + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); @@ -1631,6 +1634,10 @@ int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, Py 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; @@ -1640,10 +1647,7 @@ int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, Py return 10; } - PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); - - int minMatch = 100; - int ii; + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); @@ -1676,6 +1680,10 @@ int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, P 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; @@ -1685,10 +1693,7 @@ int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, P return 10; } - PyObject *varArgs = PyTuple_GetSlice(pyArg, idx, argCount); - - int minMatch = 100; - int ii; + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); for (ii = 0; ii < remaining; ii++) { PyObject *unpack = PyTuple_GetItem(varArgs, ii); diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index ffb9c7b553..fe19389951 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -747,6 +747,10 @@ 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 @@ -765,7 +769,7 @@ int initGlobalPyObjectVars(JNIEnv* jenv) return -1; } - JPy_JType *dictType = JType_GetTypeForName(jenv, "org.jpy.PyDictWrapper", JNI_FALSE); + dictType = JType_GetTypeForName(jenv, "org.jpy.PyDictWrapper", JNI_FALSE); if (dictType == NULL) { PyErr_Clear(); return -1; @@ -774,7 +778,7 @@ int initGlobalPyObjectVars(JNIEnv* jenv) DEFINE_METHOD(JPy_PyDictWrapper_GetPointer_MID, JPy_PyDictWrapper_JClass, "getPointer", "()J"); } - JPy_JType *keyErrorType = JType_GetTypeForName(jenv, "org.jpy.KeyError", JNI_FALSE); + keyErrorType = JType_GetTypeForName(jenv, "org.jpy.KeyError", JNI_FALSE); if (keyErrorType == NULL) { PyErr_Clear(); return -1; @@ -782,7 +786,7 @@ int initGlobalPyObjectVars(JNIEnv* jenv) JPy_KeyError_JClass = keyErrorType->classRef; } - JPy_JType *stopIterationType = JType_GetTypeForName(jenv, "org.jpy.StopIteration", JNI_FALSE); + stopIterationType = JType_GetTypeForName(jenv, "org.jpy.StopIteration", JNI_FALSE); if (stopIterationType == NULL) { PyErr_Clear(); return -1; @@ -1076,14 +1080,12 @@ void JPy_HandleJavaException(JNIEnv* jenv) if (JPy_VerboseExceptions) { char *stackTraceString; size_t stackTraceLength = 0; - - stackTraceString = strdup(""); - 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(). */ From 5f74e7882403f79968007e59088748dcfee648b5 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Tue, 8 May 2018 15:23:39 -0400 Subject: [PATCH 39/42] CHANGES.md: Updated. --- CHANGES.md | 14 +++++++++++++- doc/reference.rst | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 48308399cd..56c959a524 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,7 +24,19 @@ Improvements 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/doc/reference.rst b/doc/reference.rst index c8c4bfae7c..64650f7b3b 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -226,6 +226,13 @@ Variables 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 @@ -417,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 From b7d30241caa8f4b400b08c85a2caf07bd89516fe Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Wed, 9 May 2018 08:12:48 -0400 Subject: [PATCH 40/42] PyString and PyInt are not py3 compatible. --- doc/reference.rst | 2 +- src/main/c/jni/org_jpy_PyLib.c | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index 64650f7b3b..c6d2fb6c8b 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -393,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 -------------------------- diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index a07f44a535..c5cd13e8ec 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -1128,19 +1128,19 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - jboolean result; + int check; JPy_BEGIN_GIL_STATE - if (PyInt_Check(((PyObject*) objId))) { - result = JNI_TRUE; - } else { - result = JNI_FALSE; - } +#ifdef JPY_COMPAT_27 + check = PyInt_Check(((PyObject*) objId)); +#else + check = JPy_IS_CLONG(((PyObject*) objId)); +#endif JPy_END_GIL_STATE - return result; + return check ? (jboolean)JNI_TRUE : (jboolean)JNI_FALSE; } /** @@ -1210,7 +1210,7 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck JPy_BEGIN_GIL_STATE - if (PyString_Check(((PyObject*) objId))) { + if (JPy_IS_STR(((PyObject*) objId))) { result = JNI_TRUE; } else { result = JNI_FALSE; @@ -1267,7 +1267,7 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str pyStr = PyObject_Str(pyObject); if (pyStr) { - jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); + jObject = (*jenv)->NewStringUTF(jenv, JPy_AsUTF8_PriorToPy33(pyStr)); Py_DECREF(pyStr); } else { jObject = NULL; From 7eedc1f0ca589164d4ace5e7210e8ef827a97be1 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Wed, 9 May 2018 08:23:23 -0400 Subject: [PATCH 41/42] More Python3 string fixes, correct one not actually fix. --- src/main/c/jni/org_jpy_PyLib.c | 2 +- src/main/c/jpy_jtype.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 4fd5a1fcff..92c1bbbaa3 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -1267,7 +1267,7 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str pyStr = PyObject_Str(pyObject); if (pyStr) { - jObject = (*jenv)->NewStringUTF(jenv, JPy_AsUTF8_PriorToPy33(pyStr)); + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); Py_DECREF(pyStr); } else { jObject = NULL; diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 88cf11f831..79d4ee45aa 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -1976,7 +1976,7 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr for (ii = 0; ii < len; ++ii) { PyObject *element = PySequence_GetItem(pyArg, ii); - if (!PyString_Check(element)) { + if (!JPy_IS_STR(element)) { // if the element is not a string, this is not a good match return 0; } @@ -2016,7 +2016,7 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr return 10; } } else { - if (PyString_Check(pyArg)) { + if (JPy_IS_STR(pyArg)) { if ((*jenv)->IsAssignableFrom(jenv, JPy_JString->classRef, paramType->classRef)) { return 80; } From 21e0990a5da136e1f5d6a79a1a8366ca518832cd Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Wed, 9 May 2018 08:27:00 -0400 Subject: [PATCH 42/42] repr() was also using PyString_AS_STRING. --- src/main/c/jni/org_jpy_PyLib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 92c1bbbaa3..fa1832ac56 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -1300,7 +1300,7 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr pyStr = PyObject_Repr(pyObject); if (pyStr) { - jObject = (*jenv)->NewStringUTF(jenv, PyString_AS_STRING(pyStr)); + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); Py_DECREF(pyStr); } else { jObject = NULL;