From 1b7e59e3078f9d235a758dc3001008cdd65df54c Mon Sep 17 00:00:00 2001 From: Charles Wright Date: Wed, 14 Jun 2017 09:16:48 -0400 Subject: [PATCH 01/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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 b92272a1ee9f14f6b25bdb2ca6429c546bf59880 Mon Sep 17 00:00:00 2001 From: Marco Zuehlke Date: Thu, 8 Feb 2018 16:17:07 +0100 Subject: [PATCH 38/61] aff FTP upload to appveyor --- appveyor.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0491b3a7e8..01418dc849 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,3 +66,18 @@ install: build_script: - set PATH=%JPY_PYTHON_HOME%;%JPY_PYTHON_HOME%\DLLs;%JPY_JDK_HOME%;%JPY_JDK_HOME%\bin;%PATH% - call "%JPY_PYTHON_HOME%\python.exe" setup.py --maven bdist_wheel + - move dist win + +artifacts: + - path: 'win\*.whl' + name: wheel + +deploy: + - provider: FTP + protocol: ftp + host: ftp.brockmann-consult.de + username: jpy + password: + secure: Q/pKbj/UOz0YPzget7ghKg== + folder: software + debug: true \ No newline at end of file From 57228436944b1cea0084c8cd3a8089d6057e82bc Mon Sep 17 00:00:00 2001 From: Marco Zuehlke Date: Thu, 8 Feb 2018 16:22:37 +0100 Subject: [PATCH 39/61] add FTP upload to appveyor (fix password) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 01418dc849..a8c0b56cbf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -78,6 +78,6 @@ deploy: host: ftp.brockmann-consult.de username: jpy password: - secure: Q/pKbj/UOz0YPzget7ghKg== + secure: AMte8IErI/LRGmLGq4Y5YQ== folder: software debug: true \ No newline at end of file From 74365f0e190d0b16786590ded2d00fe9e2334653 Mon Sep 17 00:00:00 2001 From: Marco Zuehlke Date: Thu, 8 Feb 2018 16:32:52 +0100 Subject: [PATCH 40/61] update appveyor badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ac8ce4d4b..e7297f6272 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/bcdev/jpy.svg?branch=master)](https://travis-ci.org/bcdev/jpy) -[![Build Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/forman/jpy) +[![Build Status](https://ci.appveyor.com/api/projects/status/ywkcey4nlt0avasf?svg=true)](https://ci.appveyor.com/project/bcdev/jpy) [![Documentation Status](https://readthedocs.org/projects/jpy/badge/?version=latest)](http://jpy.readthedocs.org/en/latest/?badge=latest) From 9ab5d6eb5e35f4980e098f0aac925b9743dce127 Mon Sep 17 00:00:00 2001 From: Darin Petty Date: Fri, 27 Apr 2018 15:53:46 -0600 Subject: [PATCH 41/61] 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 6a878e7b2362600096a2ce149ffd1a267cbd3bb3 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Wed, 2 May 2018 18:35:19 +0200 Subject: [PATCH 42/61] Ensure that the 'name' argument is non-null, for preventing a JVM crash. --- src/main/java/org/jpy/PyObject.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index a50f52cacb..c59cee0d39 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -19,6 +19,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Map; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; @@ -228,6 +229,7 @@ public void setAttribute(String name, T value, Class valueType) */ public PyObject callMethod(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name); long pointer = PyLib.callAndReturnObject(getPointer(), true, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -244,6 +246,7 @@ public PyObject callMethod(String name, Object... args) { */ public PyObject call(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name); long pointer = PyLib.callAndReturnObject(getPointer(), false, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } From 5f74e7882403f79968007e59088748dcfee648b5 Mon Sep 17 00:00:00 2001 From: "Charles P. Wright" Date: Tue, 8 May 2018 15:23:39 -0400 Subject: [PATCH 43/61] 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 44/61] 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 45/61] 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 46/61] 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; From f1f2f59fa73cf123057976f6ca80184bb8d257cf Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Fri, 11 May 2018 16:16:38 +0200 Subject: [PATCH 47/61] Move StopIteration.java to its correct directory for its package name (org.jpy). --- src/main/java/org/jpy/{annotations => }/StopIteration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/main/java/org/jpy/{annotations => }/StopIteration.java (88%) diff --git a/src/main/java/org/jpy/annotations/StopIteration.java b/src/main/java/org/jpy/StopIteration.java similarity index 88% rename from src/main/java/org/jpy/annotations/StopIteration.java rename to src/main/java/org/jpy/StopIteration.java index ed379066a4..1e5451b95f 100644 --- a/src/main/java/org/jpy/annotations/StopIteration.java +++ b/src/main/java/org/jpy/StopIteration.java @@ -19,10 +19,10 @@ package org.jpy; /** - * Translation of Python StopIteration so that they can be programmatically detected from Java. - */ + * Translation of Python StopIteration so that they can be programmatically detected from Java. + */ public class StopIteration extends RuntimeException { StopIteration(String message) { super(message); } -} \ No newline at end of file +} From f579190c8448230da822a733cd1ce967b71d03f6 Mon Sep 17 00:00:00 2001 From: Martin Desruisseaux Date: Wed, 16 May 2018 12:35:49 +0200 Subject: [PATCH 48/61] Explicit null checks for avoiding JVM crash. https://github.com/bcdev/jpy/pull/126 --- CHANGES.md | 13 ++++----- src/main/java/org/jpy/PyModule.java | 5 ++++ src/main/java/org/jpy/PyObject.java | 41 ++++++++++++++--------------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cb858b52ac..1a571d316f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ jpy Changelog Version 0.9 =========== -This version includes a number of contributions from supportive GitHub users. Thanks to all of you! +This version includes a number of contributions from supportive GitHub users. Thanks to all of you! Fixes ----- @@ -14,17 +14,18 @@ Fixes * Fixed problem where default methods on Java 8 Interfaces were not found (issue #102). Fix by Charles P. Wright. * Fixed error caused by missing `sys.argv` in Python when called from Java (issue #81). Fix by Dave Voutila. * Fixed problem where calling jpy.get_type() too many times causes a memory access error (issue #74). Fix by Dave Voutila. -* Fixed a corruption when retrieving long values (#72). Fix by chipkent. +* Fixed a corruption when retrieving long values (#72). Fix by chipkent. * Fixed fatal error when stopping python session (issue #70, #77). Fix by Dave Voutila. +# Explicit null checks for avoiding JVM crash (issue #126). Fix by Geomatys. Improvements ------------ * Can now use pip to install Python `jpy` package directly from GitHub (#83). - This works for Linux and OS X where C compilers are available by default - and should work on Windows with Visual Studio 15 installed. - Contribution by Dave Voutila. -* Java `PyObject` is now serializable. Contribution by Mario Briggs. + This works for Linux and OS X where C compilers are available by default + and should work on Windows with Visual Studio 15 installed. + Contribution by Dave Voutila. +* Java `PyObject` is now serializable. Contribution by Mario Briggs. * Improved Varargs method matching. You may pass in either an array (as in the past) or individual Python arguments, the match for a varargs method call is the minimum match for each of the arguments. Zero length arrays (i.e. no diff --git a/src/main/java/org/jpy/PyModule.java b/src/main/java/org/jpy/PyModule.java index 9794cc7ae4..182a3b759a 100644 --- a/src/main/java/org/jpy/PyModule.java +++ b/src/main/java/org/jpy/PyModule.java @@ -16,6 +16,7 @@ package org.jpy; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; /** @@ -89,6 +90,7 @@ public static PyModule getBuiltins() { */ public static PyModule importModule(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.importModule(name); return pointer != 0 ? new PyModule(name, pointer) : null; } @@ -102,6 +104,7 @@ public static PyModule importModule(String name) { * @since 0.8 */ public static PyObject extendSysPath(String modulePath, boolean prepend) { + Objects.requireNonNull(modulePath, "path must not be null"); PyModule sys = importModule("sys"); PyObject sysPath = sys.getAttribute("path"); if (prepend) { @@ -121,8 +124,10 @@ public static PyObject extendSysPath(String modulePath, boolean prepend) { * @param The interface name. * @return A (proxy) instance implementing the given interface. */ + @Override public T createProxy(Class type) { assertPythonRuns(); + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.FUNCTION, type); } } diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index dd1710f694..36212fa0d5 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -22,7 +22,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.List; -import java.util.Map; import java.io.FileNotFoundException; import java.util.Objects; @@ -79,17 +78,13 @@ public static PyObject executeScript(String script, PyInputMode mode) throws Fil * * @param code The Python source code. * @param mode The execution mode. - * @param globals The global variables to be set. - * @param locals The locals variables to be set. + * @param globals The global variables to be set, or {@code null}. + * @param locals The locals variables to be set, or {@code null}. * @return The result of executing the code as a Python object. */ public static PyObject executeCode(String code, PyInputMode mode, Object globals, Object locals) { - if (code == null) { - throw new NullPointerException("code must not be null"); - } - if (mode == null) { - throw new NullPointerException("mode must not be null"); - } + Objects.requireNonNull(code, "code must not be null"); + Objects.requireNonNull(mode, "mode must not be null"); return new PyObject(PyLib.executeCode(code, mode.value(), globals, locals)); } @@ -102,18 +97,14 @@ public static PyObject executeCode(String code, PyInputMode mode, Object globals * * @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. + * @param globals The global variables to be set, or {@code null}. + * @param locals The locals variables to be set, or {@code null}. * @return The result of executing the script as a Python object. * @throws FileNotFoundException if the script file is not found */ public static PyObject executeScript(String script, PyInputMode mode, Object globals, Object locals) throws FileNotFoundException { - if (script == null) { - throw new NullPointerException("script must not be null"); - } - if (mode == null) { - throw new NullPointerException("mode must not be null"); - } + Objects.requireNonNull(script, "script must not be null"); + Objects.requireNonNull(mode, "mode must not be null"); return new PyObject(PyLib.executeScript(script, mode.value(), globals, locals)); } @@ -272,6 +263,7 @@ public PyDictWrapper asDict() { */ public T[] getObjectArrayValue(Class itemType) { assertPythonRuns(); + Objects.requireNonNull(itemType, "itemType must not be null"); return PyLib.getObjectArrayValue(getPointer(), itemType); } @@ -286,6 +278,7 @@ public T[] getObjectArrayValue(Class itemType) { */ public PyObject getAttribute(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.getAttributeObject(getPointer(), name); return pointer != 0 ? new PyObject(pointer) : null; } @@ -298,12 +291,13 @@ public PyObject getAttribute(String name) { * If the Python value is a wrapped Java object, it will be unwrapped. * * @param name A name of the Python attribute. - * @param valueType The type of the value or {@code null}, if unknown + * @param valueType The type of the value or {@code null}, if unknown. * @param The expected value type name. * @return The Python attribute value as Java object. */ public T getAttribute(String name, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); return PyLib.getAttributeValue(getPointer(), name, valueType); } @@ -314,11 +308,12 @@ public T getAttribute(String name, Class valueType) { * If the Java {@code value} is a wrapped Python object of type {@link PyObject}, it will be unwrapped. * * @param name A name of the Python attribute. - * @param value The new attribute value as Java object. + * @param value The new attribute value as Java object. May be {@code null}. * @param The value type name. */ public void setAttribute(String name, T value) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, value != null ? value.getClass() : null); } @@ -329,6 +324,7 @@ public void setAttribute(String name, T value) { */ public void delAttribute(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.delAttribute(getPointer(), name); } @@ -340,6 +336,7 @@ public void delAttribute(String name) { */ public boolean hasAttribute(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); return PyLib.hasAttribute(getPointer(), name); } @@ -357,6 +354,7 @@ public boolean hasAttribute(String name) { */ public void setAttribute(String name, T value, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, valueType); } @@ -372,7 +370,7 @@ public void setAttribute(String name, T value, Class valueType) */ public PyObject callMethod(String name, Object... args) { assertPythonRuns(); - Objects.requireNonNull(name); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), true, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -389,7 +387,7 @@ public PyObject callMethod(String name, Object... args) { */ public PyObject call(String name, Object... args) { assertPythonRuns(); - Objects.requireNonNull(name); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), false, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -405,6 +403,7 @@ public PyObject call(String name, Object... args) { public T createProxy(Class type) { assertPythonRuns(); //noinspection unchecked + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.METHOD, type); } From 84ad41397d0836bea51a9d278f82b96dec53831e Mon Sep 17 00:00:00 2001 From: "Dr. Irv" Date: Mon, 11 Jun 2018 16:07:56 -0400 Subject: [PATCH 49/61] Update intro.rst Fix example for interfaces --- doc/intro.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/intro.rst b/doc/intro.rst index 3380cd5f6c..0054f634a9 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -90,11 +90,8 @@ then we can call the Python code from Java as follows :: // Import the Python module PyModule plugInModule = PyLib.importModule("bibo_plugin"); - // Get the Python class - PyObject plugInClass = plugInModule.getAttribute("BiboPlugIn"); - // Call the Python class to instantiate an object - PyObject plugInObj = plugInClass.call(); + PyObject plugInObj = plugInClass.call("BiboPlugin"); // Create a Java proxy object for the Python object PlugIn plugIn = plugInObj.createProxy(PlugIn.class); From 826de3fb1b74bf7bd6c6bd96b0715d0aafbfc1da Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Tue, 12 Jun 2018 16:41:35 -0400 Subject: [PATCH 50/61] Fix iterating over dicts --- src/main/java/org/jpy/PyDictWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index b2be9247af..7bf97d0406 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -168,12 +168,13 @@ public boolean contains(Object o) { @Override public Iterator> iterator() { return new Iterator>() { + PyModule builtins = PyModule.getBuiltins(); PyObject it = pyObject.callMethod("__iter__"); PyObject next = prepareNext(); private PyObject prepareNext() { try { - return next = it.callMethod("next"); + return next = builtins.call("next", it); } catch (StopIteration e) { return next = null; } From b7adfb7b98a13d5315716803df46176418b8f032 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Wed, 13 Jun 2018 18:03:31 -0400 Subject: [PATCH 51/61] make constants get into Python correctly --- src/main/c/jpy_jtype.c | 10 +++++----- src/main/java/org/jpy/PyDictWrapper.java | 3 ++- src/test/java/org/jpy/PyObjectTest.java | 4 ++++ src/test/java/org/jpy/fixtures/Processor.java | 6 ++++++ src/test/python/fixtures/proc_class.py | 11 +++++++++-- src/test/python/fixtures/proc_module.py | 15 ++++++++++++--- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 79d4ee45aa..78f5e27208 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -284,23 +284,23 @@ PyObject* JType_ConvertJavaToPythonObject(JNIEnv* jenv, JPy_JType* type, jobject if (type->componentType == NULL) { // Scalar type, not an array, try to convert to Python equivalent - if (type == JPy_JBooleanObj) { + if (type == JPy_JBooleanObj || type == JPy_JBoolean) { jboolean value = (*jenv)->CallBooleanMethod(jenv, objectRef, JPy_Boolean_BooleanValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JBOOLEAN(value); - } else if (type == JPy_JCharacterObj) { + } else if (type == JPy_JCharacterObj || type == JPy_JChar) { jchar value = (*jenv)->CallCharMethod(jenv, objectRef, JPy_Character_CharValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JCHAR(value); - } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj) { + } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj || type == JPy_JShort || type == JPy_JInt) { jint value = (*jenv)->CallIntMethod(jenv, objectRef, JPy_Number_IntValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JINT(value); - } else if (type == JPy_JLongObj) { + } else if (type == JPy_JLongObj || type == JPy_JLong) { jlong value = (*jenv)->CallLongMethod(jenv, objectRef, JPy_Number_LongValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JLONG(value); - } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj) { + } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj || type == JPy_JFloat || type == JPy_JDouble) { jdouble value = (*jenv)->CallDoubleMethod(jenv, objectRef, JPy_Number_DoubleValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JDOUBLE(value); diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java index b2be9247af..7bf97d0406 100644 --- a/src/main/java/org/jpy/PyDictWrapper.java +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -168,12 +168,13 @@ public boolean contains(Object o) { @Override public Iterator> iterator() { return new Iterator>() { + PyModule builtins = PyModule.getBuiltins(); PyObject it = pyObject.callMethod("__iter__"); PyObject next = prepareNext(); private PyObject prepareNext() { try { - return next = it.callMethod("next"); + return next = builtins.call("next", it); } catch (StopIteration e) { return next = null; } diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index c355c8a7ab..8d2d530756 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -279,6 +279,10 @@ static void testCallProxySingleThreaded(PyObject procObject) { assertEquals("computeTile-100,200", result); result = processor.computeTile(200, 200, new float[100 * 100]); assertEquals("computeTile-200,200", result); + processor.setVal(1234); + int val = processor.getVal(); + assertEquals(val, 1234); + assertEquals(true, processor.check1234()); result = processor.dispose(); assertEquals("dispose", result); } diff --git a/src/test/java/org/jpy/fixtures/Processor.java b/src/test/java/org/jpy/fixtures/Processor.java index 29332fc372..08835f88d8 100644 --- a/src/test/java/org/jpy/fixtures/Processor.java +++ b/src/test/java/org/jpy/fixtures/Processor.java @@ -27,4 +27,10 @@ public interface Processor { String computeTile(int w, int h, float[] data); String dispose(); + + void setVal(int n); + + int getVal(); + + boolean check1234(); } diff --git a/src/test/python/fixtures/proc_class.py b/src/test/python/fixtures/proc_class.py index ce61510d8c..8e4e20df68 100644 --- a/src/test/python/fixtures/proc_class.py +++ b/src/test/python/fixtures/proc_class.py @@ -19,5 +19,12 @@ def spend_some_time(self): for i in range(10000): l.reverse() - - + def setVal(self, val): + self._val = val + + def getVal(self): + return self._val + + def check1234(self): + return self._val == 1234 + \ No newline at end of file diff --git a/src/test/python/fixtures/proc_module.py b/src/test/python/fixtures/proc_module.py index 63e633c4af..01f75e6b12 100644 --- a/src/test/python/fixtures/proc_module.py +++ b/src/test/python/fixtures/proc_module.py @@ -15,6 +15,15 @@ def spend_some_time(): for i in range(10000): l.reverse() - - - +_module_val = None +def setVal(val): + global _module_val + _module_val = val + +def getVal(): + global _module_val + return _module_val + +def check1234(): + global _module_val + return _module_val == 1234 From 47f9adc6813cfd4817952b9122b1e3470f3e6362 Mon Sep 17 00:00:00 2001 From: "Dr. Irv" Date: Mon, 18 Jun 2018 15:46:40 -0400 Subject: [PATCH 52/61] Fix Java to Python example again Just realized that I didn't edit the example right the first time. Also - can you update https://jpy.readthedocs.io with this? --- doc/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/intro.rst b/doc/intro.rst index 0054f634a9..bce161551c 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -91,7 +91,7 @@ then we can call the Python code from Java as follows :: PyModule plugInModule = PyLib.importModule("bibo_plugin"); // Call the Python class to instantiate an object - PyObject plugInObj = plugInClass.call("BiboPlugin"); + PyObject plugInObj = plugInModule.call("BiboPlugin"); // Create a Java proxy object for the Python object PlugIn plugIn = plugInObj.createProxy(PlugIn.class); From aca1e1e95170f3e637c1eb43aa0e719e88756064 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Wed, 20 Jun 2018 16:41:13 -0400 Subject: [PATCH 53/61] Support setting PYTHONHOME --- src/main/c/jni/org_jpy_PyLib.c | 62 ++++++++++++++++++++++++++++++++ src/main/c/jni/org_jpy_PyLib.h | 8 +++++ src/main/java/org/jpy/PyLib.java | 8 +++++ 3 files changed, 78 insertions(+) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index fa1832ac56..97a34109f5 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -125,6 +125,68 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning return init && JPy_Module != NULL; } +#define MAX_PYTHON_HOME 256 +#if defined(JPY_COMPAT_33P) +wchar_t staticPythonHome[MAX_PYTHON_HOME]; +#elif defined(JPY_COMPAT_27) +char staticPythonHome[MAX_PYTHON_HOME]; +#endif + +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome) +{ + #if defined(JPY_COMPAT_33P) + const wchar_t* pythonHome = NULL; + #elif defined(JPY_COMPAT_27) + const char* pythonHome = NULL; + #endif + + const char *nonWidePythonHome = NULL; + jboolean result = 0; + nonWidePythonHome = (*jenv)->GetStringUTFChars(jenv, jPythonHome, NULL); + + if (nonWidePythonHome != NULL) { + + #if defined(JPY_COMPAT_33P) + pythonHome = Py_DecodeLocale(nonWidePythonHome, NULL); + if (pythonHome != NULL) { + if (wcslen(pythonHome) < MAX_PYTHON_HOME) { + wcscpy(staticPythonHome, pythonHome); + result = 1; + } + else { + PyMem_RawFree(pythonHome); + } + + } + + #elif defined(JPY_COMPAT_27) + pythonHome = nonWidePythonHome; + if (strlen(pythonHome) < MAX_PYTHON_HOME) { + strcpy(staticPythonHome, pythonHome); + result = 1; + } + #endif + + if (result) { + Py_SetPythonHome(staticPythonHome); + + #if defined(JPY_COMPAT_33P) + PyMem_RawFree(pythonHome); + #endif + } + + (*jenv)->ReleaseStringUTFChars(jenv, jPythonHome, nonWidePythonHome); + } + + return result; +} + /* * Class: org_jpy_PyLib * Method: startPython0 diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index e4b2a3dfb0..6d870fd15f 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning (JNIEnv *, jclass); +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome); + /* * Class: org_jpy_PyLib * Method: startPython0 diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index 903e268cc6..c0302c2615 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -209,6 +209,14 @@ public static void startPython(String... extraPaths) { } static native boolean startPython0(String... paths); + + /** + * Does the equivalent of setting the PYTHONHOME environment variable. If used, + * this must be called prior to calling {@code startPython()}. + * @param pythonHome Path to Python Home (must be less than 256 characters!) + * @return true if successful, false if it fails + */ + public static native boolean setPythonHome(String pythonHome); /** * @return The Python interpreter version string. From 378c59de7112b076045cf5f8cf8dd918ddfa6b34 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Wed, 20 Jun 2018 18:19:29 -0400 Subject: [PATCH 54/61] Fix Python 3.4 issue --- src/main/c/jni/org_jpy_PyLib.c | 10 +++++++--- src/main/c/jpy_compat.h | 5 ++++- src/main/java/org/jpy/PyLib.java | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 97a34109f5..3e255d565d 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -140,7 +140,11 @@ char staticPythonHome[MAX_PYTHON_HOME]; JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome) { - #if defined(JPY_COMPAT_33P) + #if defined(JPY_COMPAT_33P) && !defined(JPY_COMPAT_35P) + return 0; // Not supported because DecodeLocale didn't exist in 3.4 + #endif + + #if defined(JPY_COMPAT_35P) const wchar_t* pythonHome = NULL; #elif defined(JPY_COMPAT_27) const char* pythonHome = NULL; @@ -152,7 +156,7 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome if (nonWidePythonHome != NULL) { - #if defined(JPY_COMPAT_33P) + #if defined(JPY_COMPAT_35P) pythonHome = Py_DecodeLocale(nonWidePythonHome, NULL); if (pythonHome != NULL) { if (wcslen(pythonHome) < MAX_PYTHON_HOME) { @@ -176,7 +180,7 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome if (result) { Py_SetPythonHome(staticPythonHome); - #if defined(JPY_COMPAT_33P) + #if defined(JPY_COMPAT_35P) PyMem_RawFree(pythonHome); #endif } diff --git a/src/main/c/jpy_compat.h b/src/main/c/jpy_compat.h index 818fb21447..62f969bc88 100644 --- a/src/main/c/jpy_compat.h +++ b/src/main/c/jpy_compat.h @@ -30,6 +30,9 @@ extern "C" { #undef JPY_COMPAT_33P #elif PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3 #define JPY_COMPAT_33P 1 +#if PY_MINOR_VERSION >= 5 +#define JPY_COMPAT_35P 1 +#endif #undef JPY_COMPAT_27 #else #error JPY_VERSION_ERROR @@ -79,4 +82,4 @@ wchar_t* JPy_AsWideCharString_PriorToPy33(PyObject *unicode, Py_ssize_t *size); #ifdef __cplusplus } /* extern "C" */ #endif -#endif /* !JPY_COMPAT_H */ \ No newline at end of file +#endif /* !JPY_COMPAT_H */ diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index c0302c2615..7c1509c42a 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -213,6 +213,7 @@ public static void startPython(String... extraPaths) { /** * Does the equivalent of setting the PYTHONHOME environment variable. If used, * this must be called prior to calling {@code startPython()}. + * Supported for Python 2.7, and Python 3.5 or higher * @param pythonHome Path to Python Home (must be less than 256 characters!) * @return true if successful, false if it fails */ From faab8f47b266220c0854cb6e8f80df8db2b936ca Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Thu, 21 Jun 2018 13:27:45 -0400 Subject: [PATCH 55/61] Fix Py3.4 and x86 --- src/main/c/jni/org_jpy_PyLib.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index 3e255d565d..53cd7ef72d 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -140,9 +140,9 @@ char staticPythonHome[MAX_PYTHON_HOME]; JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome) { - #if defined(JPY_COMPAT_33P) && !defined(JPY_COMPAT_35P) - return 0; // Not supported because DecodeLocale didn't exist in 3.4 - #endif +#if defined(JPY_COMPAT_33P) && !defined(JPY_COMPAT_35P) + return 0; // Not supported because DecodeLocale didn't exist in 3.4 +#else #if defined(JPY_COMPAT_35P) const wchar_t* pythonHome = NULL; @@ -189,6 +189,7 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome } return result; +#endif } /* From b2136a7d4be932e3b8070e98a5a750077de78686 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Fri, 22 Jun 2018 09:40:26 -0400 Subject: [PATCH 56/61] Docs for setPythonHome --- doc/install.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/install.rst b/doc/install.rst index e89f476195..d84382c589 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -90,6 +90,13 @@ All the parameters can be passed directly to the JVM either as Java system prope Such property file is also written for each build and is found in ``build/lib--/jpyconfig.properties``. +Setting PYTHONHOME +------------------ + +If the environment variable ``PYTHONHOME`` is not set when you call Python from Java, you may get an error about +file system encodings not being found. It is possible to set the location of Python from your +Java program. Use ``PyLib.setPythonHome(pathToPythonHome)`` to do that, where ``pathToPythonHome`` is a ``String`` that +contains the location of the Python installation. ======================== Build for Linux / Darwin From 7e1efd8993fa55a92c76842437cf8bfbb00c71db Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Fri, 22 Jun 2018 15:43:42 -0400 Subject: [PATCH 57/61] Support hashCode, equals, str --- src/main/java/org/jpy/PyProxyHandler.java | 100 +++++++++++++++++++++- src/test/java/org/jpy/PyObjectTest.java | 28 ++++++ src/test/python/fixtures/hasheqstr.py | 29 +++++++ 3 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/test/python/fixtures/hasheqstr.py diff --git a/src/main/java/org/jpy/PyProxyHandler.java b/src/main/java/org/jpy/PyProxyHandler.java index 862abb49dc..2dd1ec23f5 100644 --- a/src/main/java/org/jpy/PyProxyHandler.java +++ b/src/main/java/org/jpy/PyProxyHandler.java @@ -17,9 +17,11 @@ package org.jpy; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; import java.lang.reflect.Method; import java.util.Arrays; + import static org.jpy.PyLib.assertPythonRuns; /** @@ -30,7 +32,23 @@ * @since 0.7 */ class PyProxyHandler implements InvocationHandler { - private final PyObject pyObject; + // preloaded Method objects for the methods in java.lang.Object + private static Method hashCodeMethod; + private static Method equalsMethod; + private static Method toStringMethod; + static { + try { + hashCodeMethod = Object.class.getMethod("hashCode"); + equalsMethod = + Object.class.getMethod("equals", new Class[] { Object.class }); + toStringMethod = Object.class.getMethod("toString"); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + + + private final PyObject pyObject; private final PyLib.CallableKind callableKind; public PyProxyHandler(PyObject pyObject, PyLib.CallableKind callableKind) { @@ -52,13 +70,89 @@ public Object invoke(Object proxyObject, Method method, Object[] args) throws Th Long.toHexString(this.pyObject.getPointer()), Thread.currentThread()); } + String methodName = method.getName(); + Class returnType = method.getReturnType(); + if (method.equals(hashCodeMethod)) { + return callPythonHash(); + } else if (method.equals(equalsMethod)) { + if (isProxyEqualsEligible(proxyObject, args[0])) { + PyObject otherPyObject = proxyGetOtherPyObject(proxyObject, args[0]); + if (this.pyObject == otherPyObject) { + return true; + } + else { + args[0] = otherPyObject; + if (this.pyObject.hasAttribute("__eq__")) { + PyObject eqMethPtr = this.pyObject.getAttribute("__eq__"); + if (!eqMethPtr.hasAttribute("__func__")) { // Must not be implemented + return false; + } + } + else { + return false; + } + } + } + else { + return false; + } + // It's proxy eligible, but not same object, and __eq__ was implemented + // so defer to the Python __eq__ + methodName = "__eq__"; + } else if (method.equals(toStringMethod)) { + methodName = "__str__"; + } return PyLib.callAndReturnValue(this.pyObject.getPointer(), callableKind == PyLib.CallableKind.METHOD, - method.getName(), + methodName, args != null ? args.length : 0, args, method.getParameterTypes(), - method.getReturnType()); + returnType); + } + /** + * Determines if the two proxy objects implement the same interfaces + * @param proxyObject + * @param otherObject + * @return + */ + private boolean isProxyEqualsEligible(Object proxyObject, Object otherObject) { + boolean result = ((proxyObject.getClass() == otherObject.getClass()) && + (Arrays.deepEquals(proxyObject.getClass().getInterfaces(), + otherObject.getClass().getInterfaces()))); + + return result; + } + /** + * Determines the corresponding Python object for the other object passed + * @param proxyObject + * @param otherObject + * @return + */ + private PyObject proxyGetOtherPyObject(Object proxyObject, Object otherObject) { + PyObject result = null; + InvocationHandler otherProxyHandler = Proxy.getInvocationHandler(otherObject); + if (otherProxyHandler.getClass() == this.getClass()) { + PyProxyHandler otherPyProxyHandler = (PyProxyHandler) otherProxyHandler; + result = otherPyProxyHandler.pyObject; + } + + return result; + } + /** + * Calls the Python __hash__ function on the Python object, and returns the + * last 32 bits of it, since Python hash codes are 64 bits on 64 bit machines. + * @return + */ + private int callPythonHash() { + long pythonHash = PyLib.callAndReturnValue(this.pyObject.getPointer(), + true, + "__hash__", + 0, + null, + new Class[0], + Long.class); + return (int) pythonHash; } } diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index 8d2d530756..bdd0cd33d0 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -357,4 +357,32 @@ public String call() throws Exception { return processor.computeTile(x, y, new float[100 * 100]); } } + + private static interface ISimple { + public int getValue(); + } + + private static ISimple newTestObj(PyModule pyModule, String pythonClass, int value) { + PyObject procObj = pyModule.call(pythonClass, value); + ISimple simple = procObj.createProxy(ISimple.class); + return simple; + } + @Test + public void testHashEqStr() { + PyModule pyModule = PyModule.importModule("hasheqstr"); + testOneClass(pyModule, "Simple", false); + testOneClass(pyModule, "HashSimple", true); + } + private static void testOneClass(PyModule pyModule, String pythonClass, boolean eqResExpected) { + ISimple simple = newTestObj(pyModule, pythonClass, 1234); + int value = simple.getValue(); + assertEquals(value, 1234); + String rep = simple.toString(); + assertEquals(rep, pythonClass + ": 1234"); + ISimple simple2 = newTestObj(pyModule, pythonClass, 1234); + boolean eqRes = simple.equals(simple2); + assertEquals(eqRes, eqResExpected); + assertEquals(simple.hashCode()==simple2.hashCode(), eqResExpected); + assertEquals(simple.equals(simple), true); + } } diff --git a/src/test/python/fixtures/hasheqstr.py b/src/test/python/fixtures/hasheqstr.py new file mode 100644 index 0000000000..59ef87da25 --- /dev/null +++ b/src/test/python/fixtures/hasheqstr.py @@ -0,0 +1,29 @@ +class Simple(object): + def __init__(self, v): + self._v = v + + def __str__(self): + return "Simple: " + str(self._v) + + def getValue(self): + return self._v + +class HashSimple(object): + def __init__(self, v): + self._v = v + + def __str__(self): + return "HashSimple: " + str(self._v) + + def getValue(self): + return self._v + + def __hash__(self): + return hash(self._v) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self._v == other._v + else: + return False # Java can't support NotImplemented + From c66d637ac0aa17108c4b5899309571f6549e8cb7 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Mon, 25 Jun 2018 10:33:18 +0200 Subject: [PATCH 58/61] starting 0.10 --- CHANGES.md | 8 ++++++++ jpyutil.py | 6 +++--- pom.xml | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1a571d316f..2081ce235e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,14 @@ jpy Changelog ************* +Version 0.10 (in development) +============================= + +* Make jpy work with Anaconda by setting environment variable + `PYTHONHOME` from Java [#143](https://github.com/bcdev/jpy/issues/143). Contribution by Dr-Irv. +* Constants are not properly passed from Java to Python when using interfaces + [#140](https://github.com/bcdev/jpy/issues/140). Contribution by Dr-Irv. + Version 0.9 =========== diff --git a/jpyutil.py b/jpyutil.py index e4c656cc0a..5ab2463f5b 100644 --- a/jpyutil.py +++ b/jpyutil.py @@ -32,10 +32,10 @@ import subprocess -__author__ = "Norman Fomferra, Brockmann Consult GmbH" -__copyright__ = "Copyright 2015-2017 Brockmann Consult GmbH" +__author__ = "Norman Fomferra (Brockmann Consult GmbH) and contributors" +__copyright__ = "Copyright 2015-2018 Brockmann Consult GmbH and contributors" __license__ = "Apache 2.0" -__version__ = "0.9.0" +__version__ = "0.10.0.dev1" # Uncomment for debugging diff --git a/pom.xml b/pom.xml index 82c6e40ab9..c3f6fcff30 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ org.jpy jpy - 0.9.0 + 0.10.0-SNAPSHOT jar Java-Python Bridge From 36eeeec5ce8941973f6d715fddd2fe8e5daf94e4 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Mon, 25 Jun 2018 10:35:25 +0200 Subject: [PATCH 59/61] starting 0.10 --- CHANGES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2081ce235e..e004ef7249 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,9 +6,12 @@ Version 0.10 (in development) ============================= * Make jpy work with Anaconda by setting environment variable - `PYTHONHOME` from Java [#143](https://github.com/bcdev/jpy/issues/143). Contribution by Dr-Irv. -* Constants are not properly passed from Java to Python when using interfaces + `PYTHONHOME` from Java + [#143](https://github.com/bcdev/jpy/issues/143). Contribution by Dr-Irv. +* Fixed: Constants are not properly passed from Java to Python when using interfaces [#140](https://github.com/bcdev/jpy/issues/140). Contribution by Dr-Irv. +* Fixed: Cannot iterate through a dict in Python 3.x + [#136](https://github.com/bcdev/jpy/issues/136). Contribution by Dr-Irv. Version 0.9 =========== From b58cf1dd1124abec3b8d423987afad583ec6a828 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Mon, 25 Jun 2018 10:49:28 -0400 Subject: [PATCH 60/61] formatting code --- src/main/java/org/jpy/PyProxyHandler.java | 146 ++++++++++---------- src/test/java/org/jpy/PyObjectTest.java | 159 ++++++++++------------ 2 files changed, 145 insertions(+), 160 deletions(-) diff --git a/src/main/java/org/jpy/PyProxyHandler.java b/src/main/java/org/jpy/PyProxyHandler.java index 2dd1ec23f5..37ee54579e 100644 --- a/src/main/java/org/jpy/PyProxyHandler.java +++ b/src/main/java/org/jpy/PyProxyHandler.java @@ -1,17 +1,17 @@ /* * Copyright 2015 Brockmann Consult GmbH * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ package org.jpy; @@ -21,12 +21,12 @@ import java.lang.reflect.Method; import java.util.Arrays; - import static org.jpy.PyLib.assertPythonRuns; /** * The {@code InvocationHandler} for used by the proxy instances created by the - * {@link PyObject#createProxy(Class)} and {@link PyModule#createProxy(Class)} methods. + * {@link PyObject#createProxy(Class)} and {@link PyModule#createProxy(Class)} + * methods. * * @author Norman Fomferra * @since 0.7 @@ -34,23 +34,24 @@ class PyProxyHandler implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; + private static Method equalsMethod; + private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode"); - equalsMethod = - Object.class.getMethod("equals", new Class[] { Object.class }); + equalsMethod = Object.class.getMethod("equals", new Class[] { Object.class }); toStringMethod = Object.class.getMethod("toString"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } - - - private final PyObject pyObject; + + private final PyObject pyObject; + private final PyLib.CallableKind callableKind; - + public PyProxyHandler(PyObject pyObject, PyLib.CallableKind callableKind) { if (pyObject == null) { throw new NullPointerException("pyObject"); @@ -58,101 +59,94 @@ public PyProxyHandler(PyObject pyObject, PyLib.CallableKind callableKind) { this.pyObject = pyObject; this.callableKind = callableKind; } - + @Override public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable { assertPythonRuns(); - + if ((PyLib.Diag.getFlags() & PyLib.Diag.F_METH) != 0) { - System.out.printf("org.jpy.PyProxyHandler: invoke: %s(%s) on pyObject=%s in thread %s\n", - method.getName(), - Arrays.toString(args), - Long.toHexString(this.pyObject.getPointer()), - Thread.currentThread()); + System.out.printf("org.jpy.PyProxyHandler: invoke: %s(%s) on pyObject=%s in thread %s\n", method.getName(), + Arrays.toString(args), Long.toHexString(this.pyObject.getPointer()), Thread.currentThread()); } String methodName = method.getName(); Class returnType = method.getReturnType(); if (method.equals(hashCodeMethod)) { return callPythonHash(); } else if (method.equals(equalsMethod)) { - if (isProxyEqualsEligible(proxyObject, args[0])) { - PyObject otherPyObject = proxyGetOtherPyObject(proxyObject, args[0]); - if (this.pyObject == otherPyObject) { - return true; - } - else { - args[0] = otherPyObject; - if (this.pyObject.hasAttribute("__eq__")) { - PyObject eqMethPtr = this.pyObject.getAttribute("__eq__"); - if (!eqMethPtr.hasAttribute("__func__")) { // Must not be implemented - return false; - } - } - else { - return false; - } - } - } - else { - return false; - } - // It's proxy eligible, but not same object, and __eq__ was implemented - // so defer to the Python __eq__ + if (isProxyEqualsEligible(proxyObject, args[0])) { + PyObject otherPyObject = proxyGetOtherPyObject(proxyObject, args[0]); + if (this.pyObject == otherPyObject) { + return true; + } else { + args[0] = otherPyObject; + if (this.pyObject.hasAttribute("__eq__")) { + PyObject eqMethPtr = this.pyObject.getAttribute("__eq__"); + if (!eqMethPtr.hasAttribute("__func__")) { // Must not + // be + // implemented + return false; + } + } else { + return false; + } + } + } else { + return false; + } + // It's proxy eligible, but not same object, and __eq__ was + // implemented + // so defer to the Python __eq__ methodName = "__eq__"; } else if (method.equals(toStringMethod)) { methodName = "__str__"; } - - return PyLib.callAndReturnValue(this.pyObject.getPointer(), - callableKind == PyLib.CallableKind.METHOD, - methodName, - args != null ? args.length : 0, - args, - method.getParameterTypes(), - returnType); + + return PyLib.callAndReturnValue(this.pyObject.getPointer(), callableKind == PyLib.CallableKind.METHOD, + methodName, args != null ? args.length : 0, args, method.getParameterTypes(), returnType); } + /** * Determines if the two proxy objects implement the same interfaces + * * @param proxyObject * @param otherObject * @return */ private boolean isProxyEqualsEligible(Object proxyObject, Object otherObject) { - boolean result = ((proxyObject.getClass() == otherObject.getClass()) && - (Arrays.deepEquals(proxyObject.getClass().getInterfaces(), - otherObject.getClass().getInterfaces()))); - - return result; + boolean result = ((proxyObject.getClass() == otherObject.getClass()) + && (Arrays.deepEquals(proxyObject.getClass().getInterfaces(), otherObject.getClass().getInterfaces()))); + + return result; } + /** * Determines the corresponding Python object for the other object passed + * * @param proxyObject * @param otherObject * @return */ private PyObject proxyGetOtherPyObject(Object proxyObject, Object otherObject) { - PyObject result = null; - InvocationHandler otherProxyHandler = Proxy.getInvocationHandler(otherObject); - if (otherProxyHandler.getClass() == this.getClass()) { - PyProxyHandler otherPyProxyHandler = (PyProxyHandler) otherProxyHandler; - result = otherPyProxyHandler.pyObject; - } - - return result; + PyObject result = null; + InvocationHandler otherProxyHandler = Proxy.getInvocationHandler(otherObject); + if (otherProxyHandler.getClass() == this.getClass()) { + PyProxyHandler otherPyProxyHandler = (PyProxyHandler) otherProxyHandler; + result = otherPyProxyHandler.pyObject; + } + + return result; } + /** * Calls the Python __hash__ function on the Python object, and returns the - * last 32 bits of it, since Python hash codes are 64 bits on 64 bit machines. + * last 32 bits of it, since Python hash codes are 64 bits on 64 bit + * machines. + * * @return */ private int callPythonHash() { - long pythonHash = PyLib.callAndReturnValue(this.pyObject.getPointer(), - true, - "__hash__", - 0, - null, - new Class[0], - Long.class); - return (int) pythonHash; + long pythonHash = PyLib.callAndReturnValue(this.pyObject.getPointer(), true, "__hash__", 0, null, + new Class[0], Long.class); + return (int) pythonHash; } } diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index bdd0cd33d0..fac8db41fa 100644 --- a/src/test/java/org/jpy/PyObjectTest.java +++ b/src/test/java/org/jpy/PyObjectTest.java @@ -1,17 +1,17 @@ /* * Copyright 2015 Brockmann Consult GmbH * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. * * This file was modified by Illumon. * @@ -36,43 +36,44 @@ * @author Norman Fomferra */ public class PyObjectTest { - + @Before public void setUp() throws Exception { - //System.out.println("PyModuleTest: Current thread: " + Thread.currentThread()); + // System.out.println("PyModuleTest: Current thread: " + + // Thread.currentThread()); String importPath = new File("src/test/python/fixtures").getCanonicalPath(); - + PyLib.startPython(importPath); assertEquals(true, PyLib.isPythonRunning()); - + PyLib.Diag.setFlags(PyLib.Diag.F_ALL); } - + @After public void tearDown() throws Exception { PyLib.Diag.setFlags(PyLib.Diag.F_OFF); PyLib.stopPython(); } - + @Test(expected = IllegalArgumentException.class) public void testNullPointer() throws Exception { new PyObject(0); } - + @Test public void testPointer() throws Exception { long pointer = PyLib.importModule("sys"); PyObject pyObject = new PyObject(pointer); assertEquals(pointer, pyObject.getPointer()); } - + @Test public void testToString() throws Exception { long pointer = PyLib.importModule("sys"); PyObject pyObject = new PyObject(pointer); assertEquals("", pyObject.toString()); } - + @Test public void testEqualsAndHashCode() throws Exception { long pointer1 = PyLib.importModule("sys"); @@ -90,83 +91,76 @@ public void testEqualsAndHashCode() throws Exception { assertEquals(pyObject1.hashCode(), new PyObject(pointer1).hashCode()); assertTrue(pyObject1.hashCode() != pyObject2.hashCode()); } - + @Test public void testExecuteCode_Stmt() throws Exception { PyObject pyObject = PyObject.executeCode("pass", PyInputMode.STATEMENT); assertNotNull(pyObject); assertNull(pyObject.getObjectValue()); } - + @Test public void testExecuteCode_IntExpr() throws Exception { PyObject pyObject = PyObject.executeCode("7465", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals(7465, pyObject.getIntValue()); } - + @Test public void testExecuteCode_DoubleExpr() throws Exception { PyObject pyObject = PyObject.executeCode("3.14", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals(3.14, pyObject.getDoubleValue(), 1e-10); } - + @Test public void testExecuteCode_StringExpr() throws Exception { PyObject pyObject = PyObject.executeCode("'Hello from Python'", PyInputMode.EXPRESSION); assertNotNull(pyObject); assertEquals("Hello from Python", pyObject.getStringValue()); } - + @Test public void testExecuteCode_Script() throws Exception { HashMap localMap = new HashMap<>(); - PyObject pyVoid = PyObject.executeCode("" + - "import jpy\n" + - "File = jpy.get_type('java.io.File')\n" + - "f = File('test.txt')", - PyInputMode.SCRIPT, - null, - localMap); + PyObject pyVoid = PyObject.executeCode( + "" + "import jpy\n" + "File = jpy.get_type('java.io.File')\n" + "f = File('test.txt')", + PyInputMode.SCRIPT, null, localMap); assertNotNull(pyVoid); assertEquals(null, pyVoid.getObjectValue()); - + assertNotNull(localMap.get("jpy")); assertNotNull(localMap.get("File")); assertNotNull(localMap.get("f")); assertEquals(PyObject.class, localMap.get("jpy").getClass()); assertEquals(Class.class, localMap.get("File").getClass()); assertEquals(File.class, localMap.get("f").getClass()); - + assertEquals(new File("test.txt"), localMap.get("f")); } - + @Test public void testLocals() throws Exception { HashMap localMap = new HashMap<>(); localMap.put("x", 7); localMap.put("y", 6); - PyObject pyVoid = PyObject.executeCode("z = x + y", - PyInputMode.STATEMENT, - null, - localMap); + PyObject pyVoid = PyObject.executeCode("z = x + y", PyInputMode.STATEMENT, null, localMap); assertEquals(null, pyVoid.getObjectValue()); - + System.out.println("LocalMap size = " + localMap.size()); for (Map.Entry entry : localMap.entrySet()) { System.out.println("LocalMap[" + entry.getKey() + "]: " + entry.getValue()); } - + assertNotNull(localMap.get("x")); assertNotNull(localMap.get("y")); assertNotNull(localMap.get("z")); - + assertEquals(7, localMap.get("x")); assertEquals(6, localMap.get("y")); assertEquals(13, localMap.get("z")); } - + @Test public void testExecuteScript_ErrorExpr() throws Exception { try { @@ -176,7 +170,7 @@ public void testExecuteScript_ErrorExpr() throws Exception { assertTrue(e.getMessage().contains("SyntaxError")); } } - + @Test public void testCall() throws Exception { // Python equivalent: @@ -187,16 +181,16 @@ public void testCall() throws Exception { // PyModule builtins; try { - //Python 3.3 + // Python 3.3 builtins = PyModule.importModule("builtins"); } catch (Exception e) { - //Python 2.7 + // Python 2.7 builtins = PyModule.importModule("__builtin__"); } PyObject value = builtins.call("max", "A", "Z"); Assert.assertEquals("Z", value.getStringValue()); } - + @Test public void testGetSetAttributes() throws Exception { // Python equivalent: @@ -215,7 +209,7 @@ public void testGetSetAttributes() throws Exception { PyObject a = myobj.getAttribute("a"); Assert.assertEquals("Tut tut!", a.getStringValue()); } - + private boolean hasKey(Map dict, String key) { for (Map.Entry entry : dict.entrySet()) { if (entry.getKey().isString()) { @@ -226,48 +220,47 @@ private boolean hasKey(Map dict, String key) { } return false; } - + @Test public void testDictCopy() throws Exception { PyObject globals = PyLib.getMainGlobals(); PyDictWrapper dict = globals.asDict(); PyDictWrapper dictCopy = dict.copy(); - + PyObject.executeCode("x = 42", PyInputMode.STATEMENT, globals, dictCopy.unwrap()); - + boolean copyHasX = hasKey(dictCopy, "x"); boolean origHasX = hasKey(dict, "x"); - + assertTrue(copyHasX); assertFalse(origHasX); } - + @Test public void testCreateProxyAndCallSingleThreaded() throws Exception { - //addTestDirToPythonSysPath(); + // addTestDirToPythonSysPath(); PyModule procModule = PyModule.importModule("proc_class"); PyObject procObj = procModule.call("Processor"); testCallProxySingleThreaded(procObj); } - + // see https://github.com/bcdev/jpy/issues/26 @Test public void testCreateProxyAndCallMultiThreaded() throws Exception { - //addTestDirToPythonSysPath(); - //PyLib.Diag.setFlags(PyLib.Diag.F_ALL); + // addTestDirToPythonSysPath(); + // PyLib.Diag.setFlags(PyLib.Diag.F_ALL); PyModule procModule = PyModule.importModule("proc_class"); PyObject procObj = procModule.call("Processor"); PyLib.Diag.setFlags(PyLib.Diag.F_ALL); testCallProxyMultiThreaded(procObj); - //PyLib.Diag.setFlags(PyLib.Diag.F_OFF); + // PyLib.Diag.setFlags(PyLib.Diag.F_OFF); } - - + static void testCallProxySingleThreaded(PyObject procObject) { // Cast the Python object to a Java object of type 'Processor' Processor processor = procObject.createProxy(Processor.class); assertNotNull(processor); - + String result; result = processor.initialize(); assertEquals("initialize", result); @@ -286,39 +279,35 @@ static void testCallProxySingleThreaded(PyObject procObject) { result = processor.dispose(); assertEquals("dispose", result); } - + static void testCallProxyMultiThreaded(PyObject procObject) { testCallProxyMultiThreaded(procObject, Executors.newFixedThreadPool(4)); } - + private static void testCallProxyMultiThreaded(PyObject procObject, ExecutorService executorService) { // Cast the Python object to a Java object of type 'Processor' final Processor processor = procObject.createProxy(Processor.class); assertNotNull(processor); - + String result; result = processor.initialize(); assertEquals("initialize", result); - + List> futures; try { - futures = executorService.invokeAll(Arrays.asList(new ProcessorTask(processor, 100, 100), - new ProcessorTask(processor, 200, 100), - new ProcessorTask(processor, 100, 200), - new ProcessorTask(processor, 200, 200)), 10, TimeUnit.SECONDS); - + futures = executorService.invokeAll( + Arrays.asList(new ProcessorTask(processor, 100, 100), new ProcessorTask(processor, 200, 100), + new ProcessorTask(processor, 100, 200), new ProcessorTask(processor, 200, 200)), + 10, TimeUnit.SECONDS); + result = processor.dispose(); assertEquals("dispose", result); - - String[] results = new String[]{ - futures.get(0).get(), - futures.get(1).get(), - futures.get(2).get(), - futures.get(3).get(), - }; - + + String[] results = new String[] { futures.get(0).get(), futures.get(1).get(), futures.get(2).get(), + futures.get(3).get(), }; + Arrays.sort(results); - + result = results[0]; assertEquals("computeTile-100,100", result); result = results[1]; @@ -327,31 +316,31 @@ private static void testCallProxyMultiThreaded(PyObject procObject, ExecutorServ assertEquals("computeTile-200,100", result); result = results[3]; assertEquals("computeTile-200,200", result); - + } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); fail(e.getMessage()); } } - + static void addTestDirToPythonSysPath() throws IOException { // Add module dir to sys.path in order to import file 'proc_class.py' String importPath = new File("src/test/python/fixtures").getCanonicalPath(); - //System.out.println("importPath = " + importPath); + // System.out.println("importPath = " + importPath); PyLib.execScript(String.format("import sys; sys.path.append('%s')", importPath.replace("\\", "\\\\"))); } - + private static class ProcessorTask implements Callable { final Processor processor; int x; int y; - + public ProcessorTask(Processor processor, int x, int y) { this.processor = processor; this.x = x; this.y = y; } - + @Override public String call() throws Exception { return processor.computeTile(x, y, new float[100 * 100]); @@ -359,7 +348,7 @@ public String call() throws Exception { } private static interface ISimple { - public int getValue(); + public int getValue(); } private static ISimple newTestObj(PyModule pyModule, String pythonClass, int value) { @@ -367,12 +356,14 @@ private static ISimple newTestObj(PyModule pyModule, String pythonClass, int val ISimple simple = procObj.createProxy(ISimple.class); return simple; } + @Test public void testHashEqStr() { PyModule pyModule = PyModule.importModule("hasheqstr"); testOneClass(pyModule, "Simple", false); testOneClass(pyModule, "HashSimple", true); } + private static void testOneClass(PyModule pyModule, String pythonClass, boolean eqResExpected) { ISimple simple = newTestObj(pyModule, pythonClass, 1234); int value = simple.getValue(); @@ -382,7 +373,7 @@ private static void testOneClass(PyModule pyModule, String pythonClass, boolean ISimple simple2 = newTestObj(pyModule, pythonClass, 1234); boolean eqRes = simple.equals(simple2); assertEquals(eqRes, eqResExpected); - assertEquals(simple.hashCode()==simple2.hashCode(), eqResExpected); + assertEquals(simple.hashCode() == simple2.hashCode(), eqResExpected); assertEquals(simple.equals(simple), true); } } From 86fce2cdbdcd86109b87f669b980929ebc22cc29 Mon Sep 17 00:00:00 2001 From: Dr-Irv Date: Mon, 25 Jun 2018 11:02:31 -0400 Subject: [PATCH 61/61] Merge in master --- CHANGES.md | 38 +- README.md | 2 +- appveyor.yml | 15 + doc/intro.rst | 5 +- doc/reference.rst | 19 +- jpyutil.py | 4 +- pom.xml | 2 +- setup.py | 4 +- src/main/c/jni/org_jpy_PyLib.c | 1247 +++++++++++++++-- src/main/c/jni/org_jpy_PyLib.h | 172 ++- src/main/c/jpy_compat.h | 5 +- src/main/c/jpy_conv.c | 8 +- src/main/c/jpy_conv.h | 4 +- src/main/c/jpy_jmethod.c | 204 ++- src/main/c/jpy_jmethod.h | 9 +- src/main/c/jpy_jobj.c | 31 +- src/main/c/jpy_jobj.h | 7 +- src/main/c/jpy_jtype.c | 489 ++++++- src/main/c/jpy_jtype.h | 12 +- src/main/c/jpy_module.c | 304 +++- src/main/c/jpy_module.h | 29 + src/main/c/jpy_verboseexcept.c | 97 ++ src/main/c/jpy_verboseexcept.h | 37 + src/main/java/org/jpy/KeyError.java | 28 + src/main/java/org/jpy/PyDictWrapper.java | 258 ++++ src/main/java/org/jpy/PyLib.java | 62 +- src/main/java/org/jpy/PyListWrapper.java | 213 +++ src/main/java/org/jpy/PyModule.java | 5 + src/main/java/org/jpy/PyObject.java | 183 ++- src/main/java/org/jpy/StopIteration.java | 28 + src/test/java/org/jpy/PyLibTest.java | 35 + src/test/java/org/jpy/PyObjectTest.java | 63 +- .../CovariantOverloadExtendTestFixture.java | 38 + .../CovariantOverloadTestFixture.java | 44 + .../jpy/fixtures/ExceptionTestFixture.java | 27 + .../fixtures/MethodOverloadTestFixture.java | 10 + src/test/java/org/jpy/fixtures/Processor.java | 6 + .../fixtures/TypeTranslationTestFixture.java | 34 + .../org/jpy/fixtures/VarArgsTestFixture.java | 129 ++ src/test/python/fixtures/proc_class.py | 11 +- src/test/python/fixtures/proc_module.py | 15 +- src/test/python/jpy_exception_test.py | 42 +- src/test/python/jpy_overload_test.py | 87 +- src/test/python/jpy_translation_test.py | 42 + 44 files changed, 3805 insertions(+), 299 deletions(-) create mode 100644 src/main/c/jpy_verboseexcept.c create mode 100644 src/main/c/jpy_verboseexcept.h create mode 100644 src/main/java/org/jpy/KeyError.java create mode 100644 src/main/java/org/jpy/PyDictWrapper.java create mode 100644 src/main/java/org/jpy/PyListWrapper.java create mode 100644 src/main/java/org/jpy/StopIteration.java create mode 100644 src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java create mode 100644 src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java 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/CHANGES.md b/CHANGES.md index 196efc385d..e004ef7249 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,21 @@ jpy Changelog ************* +Version 0.10 (in development) +============================= + +* Make jpy work with Anaconda by setting environment variable + `PYTHONHOME` from Java + [#143](https://github.com/bcdev/jpy/issues/143). Contribution by Dr-Irv. +* Fixed: Constants are not properly passed from Java to Python when using interfaces + [#140](https://github.com/bcdev/jpy/issues/140). Contribution by Dr-Irv. +* Fixed: Cannot iterate through a dict in Python 3.x + [#136](https://github.com/bcdev/jpy/issues/136). Contribution by Dr-Irv. + Version 0.9 =========== -This version includes a number of contributions from supportive GitHub users. Thanks to all of you! +This version includes a number of contributions from supportive GitHub users. Thanks to all of you! Fixes ----- @@ -14,18 +25,31 @@ Fixes * Fixed problem where default methods on Java 8 Interfaces were not found (issue #102). Fix by Charles P. Wright. * Fixed error caused by missing `sys.argv` in Python when called from Java (issue #81). Fix by Dave Voutila. * Fixed problem where calling jpy.get_type() too many times causes a memory access error (issue #74). Fix by Dave Voutila. -* Fixed a corruption when retrieving long values (#72). Fix by chipkent. +* Fixed a corruption when retrieving long values (#72). Fix by chipkent. * Fixed fatal error when stopping python session (issue #70, #77). Fix by Dave Voutila. +# Explicit null checks for avoiding JVM crash (issue #126). Fix by Geomatys. Improvements ------------ * Can now use pip to install Python `jpy` package directly from GitHub (#83). - This works for Linux and OS X where C compilers are available by default - and should work on Windows with Visual Studio 15 installed. - Contribution by Dave Voutila. -* Java `PyObject` is now serializable. Contribution by Mario Briggs. - + This works for Linux and OS X where C compilers are available by default + and should work on Windows with Visual Studio 15 installed. + Contribution by Dave Voutila. +* Java `PyObject` is now serializable. Contribution by Mario Briggs. +* Improved Varargs method matching. You may pass in either an array (as in the + past) or individual Python arguments, the match for a varargs method call is + the minimum match for each of the arguments. Zero length arrays (i.e. no + arguments) are also permitted with a match value of 10. +* `jpy.type_translations` dictionary for callbacks when instantiating Python objects. +* `jpy.VerboseExceptions` enables full Java stack traces. +* More Python exceptions are translated to the corresponding Java type. +* Globals and locals are converted when executing code with PyLib, to allow variables to be + used across statement invocation; and interrogated from Java. +* PyObject wrappers for dictionary, list, and introspection functions to tell + you whether or not you can convert the object. +* Support for isAssignable checks when dealing with Python Strings and primitives, to allow + matches for argument types such as `java.lang.Comparable` or `java.lang.Number`. Version 0.8 =========== diff --git a/README.md b/README.md index 4ac8ce4d4b..e7297f6272 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/bcdev/jpy.svg?branch=master)](https://travis-ci.org/bcdev/jpy) -[![Build Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/forman/jpy) +[![Build Status](https://ci.appveyor.com/api/projects/status/ywkcey4nlt0avasf?svg=true)](https://ci.appveyor.com/project/bcdev/jpy) [![Documentation Status](https://readthedocs.org/projects/jpy/badge/?version=latest)](http://jpy.readthedocs.org/en/latest/?badge=latest) diff --git a/appveyor.yml b/appveyor.yml index 0491b3a7e8..a8c0b56cbf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -66,3 +66,18 @@ install: build_script: - set PATH=%JPY_PYTHON_HOME%;%JPY_PYTHON_HOME%\DLLs;%JPY_JDK_HOME%;%JPY_JDK_HOME%\bin;%PATH% - call "%JPY_PYTHON_HOME%\python.exe" setup.py --maven bdist_wheel + - move dist win + +artifacts: + - path: 'win\*.whl' + name: wheel + +deploy: + - provider: FTP + protocol: ftp + host: ftp.brockmann-consult.de + username: jpy + password: + secure: AMte8IErI/LRGmLGq4Y5YQ== + folder: software + debug: true \ No newline at end of file diff --git a/doc/intro.rst b/doc/intro.rst index 3380cd5f6c..bce161551c 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -90,11 +90,8 @@ then we can call the Python code from Java as follows :: // Import the Python module PyModule plugInModule = PyLib.importModule("bibo_plugin"); - // Get the Python class - PyObject plugInClass = plugInModule.getAttribute("BiboPlugIn"); - // Call the Python class to instantiate an object - PyObject plugInObj = plugInClass.call(); + PyObject plugInObj = plugInModule.call("BiboPlugin"); // Create a Java proxy object for the Python object PlugIn plugIn = plugInObj.createProxy(PlugIn.class); diff --git a/doc/reference.rst b/doc/reference.rst index bb6fd55f3b..c6d2fb6c8b 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -218,6 +218,20 @@ Variables Here a call to the ``read`` method will modify the numpy array's content as desired and return the same array instance as indicated by the Java method's specification. +.. py:data:: type_translations + :module: jpy + + Contains callbacks which are called when instantiating a Python object from a Java object. + After the standard wrapping of the Java object as a Python object, the Java type name is looked up in this + dictionary. If the returned item is a callable, the callable is called with the JPy object as an argument, + and the callable's result is returned to the user. + + +.. py:data:: VerboseExceptions.enabled + :module: jpy + + If set to true, then jpy will produce more verbose exception messages; which include the full Java stack trace. + If set to false, then jpy produces exceptions using only the underlying Java exception's toString method. .. py:data:: diag :module: jpy @@ -379,7 +393,7 @@ Java object types | ``java.lang.String`` | 1 | 0 | 0 | 0 | 100 | +-------------------------+--------------+----------+---------+------------+---------+ | ``java.lang.Object`` | 1 | 10 | 10 | 10 | 10 | -+-------------------------+--------------+----------+---------+------------+---------+ ++-------------------------+--------------+----------+---------+------------+---------+jpy Java primitive array types -------------------------- @@ -410,6 +424,9 @@ given above, the a match value of 10 applies, as long as the item size of a buff Java object array types ----------------------- +For String arrays, if a sequence is matched with a value of 80 if all the elements in the sequence are Python strings. + + todo diff --git a/jpyutil.py b/jpyutil.py index ea15f43cc2..7d080c89d2 100644 --- a/jpyutil.py +++ b/jpyutil.py @@ -32,8 +32,8 @@ import subprocess -__author__ = "Norman Fomferra, Brockmann Consult GmbH" -__copyright__ = "Copyright 2015-2017 Brockmann Consult GmbH" +__author__ = "Norman Fomferra (Brockmann Consult GmbH) and contributors" +__copyright__ = "Copyright 2015-2018 Brockmann Consult GmbH and contributors" __license__ = "Apache 2.0" __version__ = "0.9.1.dev1" diff --git a/pom.xml b/pom.xml index fc7ae1f646..c587e9d8c8 100644 --- a/pom.xml +++ b/pom.xml @@ -188,4 +188,4 @@ - \ No newline at end of file + diff --git a/setup.py b/setup.py index 0aea809b08..c9d1ea74b3 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ sources = [ os.path.join(src_main_c_dir, 'jpy_module.c'), os.path.join(src_main_c_dir, 'jpy_diag.c'), + os.path.join(src_main_c_dir, 'jpy_verboseexcept.c'), os.path.join(src_main_c_dir, 'jpy_conv.c'), os.path.join(src_main_c_dir, 'jpy_compat.c'), os.path.join(src_main_c_dir, 'jpy_jtype.c'), @@ -98,6 +99,7 @@ os.path.join(src_test_py_dir, 'jpy_typeconv_test.py'), os.path.join(src_test_py_dir, 'jpy_typeres_test.py'), os.path.join(src_test_py_dir, 'jpy_modretparam_test.py'), + os.path.join(src_test_py_dir, 'jpy_translation_test.py'), os.path.join(src_test_py_dir, 'jpy_gettype_test.py'), ] @@ -200,7 +202,7 @@ def test_python_java_classes(): """ Run Python tests against JPY test classes """ sub_env = {'PYTHONPATH': _build_dir()} - log.info('Executing Python unit tests (against Java runtime classes)...') + log.info('Executing Python unit tests (against JPY test classes)...') return jpyutil._execute_python_scripts(python_java_jpy_tests, env=sub_env) diff --git a/src/main/c/jni/org_jpy_PyLib.c b/src/main/c/jni/org_jpy_PyLib.c index ef2a05056f..53cd7ef72d 100644 --- a/src/main/c/jni/org_jpy_PyLib.c +++ b/src/main/c/jni/org_jpy_PyLib.c @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #include @@ -32,7 +35,12 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyValue, jstring jName); PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyValue, jboolean isMethodCall, jstring jName, jint argCount, jobjectArray jArgs, jobjectArray jParamClasses); void PyLib_HandlePythonException(JNIEnv* jenv); +void PyLib_ThrowOOM(JNIEnv* jenv); +void PyLib_ThrowFNFE(JNIEnv* jenv, const char *file); +void PyLib_ThrowUOE(JNIEnv* jenv, const char *message); +void PyLib_ThrowRTE(JNIEnv* jenv, const char *message); void PyLib_RedirectStdOut(void); +int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap); static int JPy_InitThreads = 0; @@ -117,6 +125,73 @@ JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning return init && JPy_Module != NULL; } +#define MAX_PYTHON_HOME 256 +#if defined(JPY_COMPAT_33P) +wchar_t staticPythonHome[MAX_PYTHON_HOME]; +#elif defined(JPY_COMPAT_27) +char staticPythonHome[MAX_PYTHON_HOME]; +#endif + +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome) +{ +#if defined(JPY_COMPAT_33P) && !defined(JPY_COMPAT_35P) + return 0; // Not supported because DecodeLocale didn't exist in 3.4 +#else + + #if defined(JPY_COMPAT_35P) + const wchar_t* pythonHome = NULL; + #elif defined(JPY_COMPAT_27) + const char* pythonHome = NULL; + #endif + + const char *nonWidePythonHome = NULL; + jboolean result = 0; + nonWidePythonHome = (*jenv)->GetStringUTFChars(jenv, jPythonHome, NULL); + + if (nonWidePythonHome != NULL) { + + #if defined(JPY_COMPAT_35P) + pythonHome = Py_DecodeLocale(nonWidePythonHome, NULL); + if (pythonHome != NULL) { + if (wcslen(pythonHome) < MAX_PYTHON_HOME) { + wcscpy(staticPythonHome, pythonHome); + result = 1; + } + else { + PyMem_RawFree(pythonHome); + } + + } + + #elif defined(JPY_COMPAT_27) + pythonHome = nonWidePythonHome; + if (strlen(pythonHome) < MAX_PYTHON_HOME) { + strcpy(staticPythonHome, pythonHome); + result = 1; + } + #endif + + if (result) { + Py_SetPythonHome(staticPythonHome); + + #if defined(JPY_COMPAT_35P) + PyMem_RawFree(pythonHome); + #endif + } + + (*jenv)->ReleaseStringUTFChars(jenv, jPythonHome, nonWidePythonHome); + } + + return result; +#endif +} + /* * Class: org_jpy_PyLib * Method: startPython0 @@ -291,18 +366,26 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript (JNIEnv* jenv, jclass jLibClass, jstring jScript) { const char* scriptChars; - int retCode; + int retCode = -1; JPy_BEGIN_GIL_STATE scriptChars = (*jenv)->GetStringUTFChars(jenv, jScript, NULL); + if (scriptChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_execScript: script='%s'\n", scriptChars); retCode = PyRun_SimpleString(scriptChars); if (retCode < 0) { JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_execScript: error: PyRun_SimpleString(\"%s\") returned %d\n", scriptChars, retCode); // Note that we cannot retrieve last Python exception after a calling PyRun_SimpleString, see documentation of PyRun_SimpleString. } - (*jenv)->ReleaseStringUTFChars(jenv, jScript, scriptChars); + +error: + if (scriptChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jScript, scriptChars); + } JPy_END_GIL_STATE @@ -319,7 +402,7 @@ PyObject* PyLib_ConvertJavaToPythonObject(JNIEnv* jenv, jobject jObject) int PyLib_ConvertPythonToJavaObject(JNIEnv* jenv, PyObject* pyObject, jobject* jObject) { - return JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, pyObject, jObject); + return JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, pyObject, jObject, JNI_FALSE); } void dumpDict(const char* dictName, PyObject* dict) @@ -329,6 +412,11 @@ void dumpDict(const char* dictName, PyObject* dict) Py_ssize_t pos = 0; Py_ssize_t i = 0; + if (!PyDict_Check(dict)) { + printf(">> dumpDict: %s is not a dictionary!\n", dictName); + return; + } + size = PyDict_Size(dict); printf(">> dumpDict: %s.size = %ld\n", dictName, size); while (PyDict_Next(dict, &pos, &key, &value)) { @@ -339,231 +427,953 @@ void dumpDict(const char* dictName, PyObject* dict) } } - -JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode - (JNIEnv* jenv, jclass jLibClass, jstring jCode, jint jStart, jobject jGlobals, jobject jLocals) -{ - const char* codeChars; - PyObject* pyReturnValue; - PyObject* pyGlobals; - PyObject* pyLocals; +/** + * Get the globals from the __main__ module. + */ +PyObject *getMainGlobals() { PyObject* pyMainModule; + PyObject* pyGlobals; + + JPy_BEGIN_GIL_STATE + + pyMainModule = PyImport_AddModule("__main__"); // borrowed ref + + if (pyMainModule == NULL) { + return NULL; + } + + + pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref + + JPy_END_GIL_STATE + + return pyGlobals; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getMainGlobals + (JNIEnv *jenv, jclass libClass) { + jobject objectRef; + + PyObject *globals = getMainGlobals(); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, globals, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_copyDict + (JNIEnv *jenv, jclass libClass, jlong pyPointer) { + jobject objectRef; + PyObject *src, *copy; + + src = (PyObject*)pyPointer; + + if (!PyDict_Check(src)) { + PyLib_ThrowUOE(jenv, "Not a dictionary!"); + return NULL; + } + + copy = PyDict_Copy(src); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, copy, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_newDict + (JNIEnv *jenv, jclass libClass) { + jobject objectRef; + PyObject *dict; + + dict = PyDict_New(); + + if (JType_ConvertPythonToJavaObject(jenv, JPy_JPyObject, dict, &objectRef, JNI_FALSE) < 0) { + return NULL; + } + + return objectRef; +} + +/** + * Copies a Java Map into a new Python dictionary. + */ +PyObject *copyJavaStringObjectMapToPyDict(JNIEnv *jenv, jobject jMap) { + PyObject *result; + jobject entrySet, iterator, mapEntry; + jboolean hasNext; + + result = PyDict_New(); + if (result == NULL) { + return result; + } + + entrySet = (*jenv)->CallObjectMethod(jenv, jMap, JPy_Map_entrySet_MID); + if (entrySet == NULL) { + goto error; + } + + iterator = (*jenv)->CallObjectMethod(jenv, entrySet, JPy_Set_Iterator_MID); + if (iterator == NULL) { + goto error; + } + + hasNext = (*jenv)->CallBooleanMethod(jenv, iterator, JPy_Iterator_hasNext_MID); + + while (hasNext) { + jobject key, value; + char const *keyChars; + PyObject *pyKey; + PyObject *pyValue; + JPy_JType* type; + + mapEntry = (*jenv)->CallObjectMethod(jenv, iterator, JPy_Iterator_next_MID); + if (mapEntry == NULL) { + goto error; + } + + key = (*jenv)->CallObjectMethod(jenv, mapEntry, JPy_Map_Entry_getKey_MID); + if (key == NULL) { + goto error; + } + + // we require string keys + if (!(*jenv)->IsInstanceOf(jenv, key, JPy_String_JClass)) { + goto error; + } + keyChars = (*jenv)->GetStringUTFChars(jenv, (jstring)key, NULL); + if (keyChars == NULL) { + goto error; + } + + pyKey = JPy_FROM_CSTR(keyChars); + (*jenv)->ReleaseStringUTFChars(jenv, (jstring)key, keyChars); + + value = (*jenv)->CallObjectMethod(jenv, mapEntry, JPy_Map_Entry_getValue_MID); + + type = JType_GetTypeForObject(jenv, value); + pyValue = JType_ConvertJavaToPythonObject(jenv, type, value); + + PyDict_SetItem(result, pyKey, pyValue); + + hasNext = (*jenv)->CallBooleanMethod(jenv, iterator, JPy_Iterator_hasNext_MID); + } + + return result; + +error: + if (result != NULL) { + Py_XDECREF(result); + } + return NULL; +} + +int copyPythonDictToJavaMap(JNIEnv *jenv, PyObject *pyDict, jobject jMap) { + PyObject *pyKey, *pyValue; + Py_ssize_t pos = 0; + Py_ssize_t dictSize; + jobject *jValues = NULL; + jobject *jKeys = NULL; + int ii; + jboolean exceptionAlready = JNI_FALSE; + jthrowable savedException = NULL; + int retcode = -1; + + if (!PyDict_Check(pyDict)) { + PyLib_ThrowUOE(jenv, "PyObject is not a dictionary!"); + return -1; + } + + dictSize = PyDict_Size(pyDict); + + jKeys = malloc(dictSize * sizeof(jobject)); + jValues = malloc(dictSize * sizeof(jobject)); + if (jKeys == NULL || jValues == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + + exceptionAlready = (*jenv)->ExceptionCheck(jenv); + if (exceptionAlready) { + // save the exception away, because otherwise the conversion methods might spuriously fail + savedException = (*jenv)->ExceptionOccurred(jenv); + (*jenv)->ExceptionClear(jenv); + } + + // first convert everything + ii = 0; + while (PyDict_Next(pyDict, &pos, &pyKey, &pyValue)) { + if (JPy_AsJObjectWithClass(jenv, pyKey, &(jKeys[ii]), JPy_String_JClass) < 0) { + // an error occurred + goto error; + } + if (JPy_AsJObject(jenv, pyValue, &(jValues[ii]), JNI_TRUE) < 0) { + // an error occurred + goto error; + } + ii++; + } + + // now that we've converted, clear out the map and repopulate it + (*jenv)->CallVoidMethod(jenv, jMap, JPy_Map_clear_MID); + for (ii = 0; ii < dictSize; ++ii) { + // since the map is cleared, we want to plow through all of the put operations + (*jenv)->CallObjectMethod(jenv, jMap, JPy_Map_put_MID, jKeys[ii], jValues[ii]); + } + // and we are successful! + retcode = 0; + +error: + if (exceptionAlready) { + // restore our original exception + (*jenv)->Throw(jenv, savedException); + } + + free(jKeys); + free(jValues); + return retcode; +} + +typedef PyObject * (*DoRun)(const void *,int,PyObject*,PyObject*); + +/** + * After setting up the globals and locals, calls the provided function. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + * + */ +jlong executeInternal(JNIEnv* jenv, jclass jLibClass, jint jStart, jobject jGlobals, jobject jLocals, DoRun runFunction, void *runArg) { + PyObject *pyReturnValue; + PyObject *pyGlobals; + PyObject *pyLocals; int start; + jboolean decGlobals, decLocals, copyGlobals, copyLocals; JPy_BEGIN_GIL_STATE + decGlobals = decLocals = JNI_FALSE; + copyGlobals = copyLocals = JNI_FALSE; pyGlobals = NULL; pyLocals = NULL; pyReturnValue = NULL; - pyMainModule = NULL; - codeChars = NULL; - pyMainModule = PyImport_AddModule("__main__"); // borrowed ref - if (pyMainModule == NULL) { + if (jGlobals == NULL) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using main globals\n"); + pyGlobals = getMainGlobals(); + if (pyGlobals == NULL) { + PyLib_HandlePythonException(jenv); + goto error; + } + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_PyObject_JClass)) { + // if we are an instance of PyObject, just use the object + pyGlobals = (PyObject *)((*jenv)->CallLongMethod(jenv, jGlobals, JPy_PyObject_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyObject globals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_PyDictWrapper_JClass)) { + // if we are an instance of a wrapped dictionary, just use the underlying dictionary + pyGlobals = (PyObject *)((*jenv)->CallLongMethod(jenv, jGlobals, JPy_PyDictWrapper_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyDictWrapper globals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jGlobals, JPy_Map_JClass)) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using Java Map globals\n"); + // this is a java Map and we need to convert it + pyGlobals = copyJavaStringObjectMapToPyDict(jenv, jGlobals); + if (pyGlobals == NULL) { + PyLib_ThrowRTE(jenv, "Could not convert globals from Java Map to Python dictionary"); + goto error; + } + copyGlobals = decGlobals = JNI_TRUE; + } else { + PyLib_ThrowUOE(jenv, "Unsupported globals type"); + goto error; + } + + // if we have no locals specified, using globals for it matches the behavior of eval, "The expression argument is + // parsed and evaluated as a Python expression (technically speaking, a condition list) using the globals and + // locals dictionaries as global and local namespace. ... If the locals dictionary is omitted it defaults to the + // globals dictionary. If both dictionaries are omitted, the expression is executed in the environment where eval() + // is called. The return value is the result of the evaluated expression. + if (jLocals == NULL) { + pyLocals = pyGlobals; + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using globals for locals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_PyObject_JClass)) { + // if we are an instance of PyObject, just use the object + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyObject locals\n"); + pyLocals = (PyObject *)((*jenv)->CallLongMethod(jenv, jLocals, JPy_PyObject_GetPointer_MID)); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_PyDictWrapper_JClass)) { + // if we are an instance of a wrapped dictionary, just use the underlying dictionary + pyLocals = (PyObject *)((*jenv)->CallLongMethod(jenv, jLocals, JPy_PyDictWrapper_GetPointer_MID)); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using PyDictWrapper locals\n"); + } else if ((*jenv)->IsInstanceOf(jenv, jLocals, JPy_Map_JClass)) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: using Java Map locals\n"); + // this is a java Map and we need to convert it + pyLocals = copyJavaStringObjectMapToPyDict(jenv, jLocals); + if (pyLocals == NULL) { + PyLib_ThrowRTE(jenv, "Could not convert locals from Java Map to Python dictionary"); + goto error; + } + copyLocals = decLocals = JNI_TRUE; + } else { + PyLib_ThrowUOE(jenv, "Unsupported locals type"); + goto error; + } + + start = jStart == JPy_IM_STATEMENT ? Py_single_input : + jStart == JPy_IM_SCRIPT ? Py_file_input : + Py_eval_input; + + pyReturnValue = runFunction(runArg, start, pyGlobals, pyLocals); + if (pyReturnValue == NULL) { + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: Handle Python Exception\n"); PyLib_HandlePythonException(jenv); goto error; } +error: + if (copyGlobals) { + copyPythonDictToJavaMap(jenv, pyGlobals, jGlobals); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: copied back Java global\n"); + } + if (copyLocals) { + copyPythonDictToJavaMap(jenv, pyLocals, jLocals); + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeInternal: copied back Java locals\n"); + } + if (decGlobals) { + Py_XDECREF(pyGlobals); + } + if (decLocals) { + Py_XDECREF(pyLocals); + } + + JPy_END_GIL_STATE + + return (jlong) pyReturnValue; +} + +PyObject *pyRunStringWrapper(const char *code, int start, PyObject *globals, PyObject *locals) { + PyObject *result = PyRun_String(code, start, globals, locals); + return result; +} + +/** + * Calls PyRun_String under the covers to execute the string contents. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + */ +JNIEXPORT +jlong JNICALL Java_org_jpy_PyLib_executeCode + (JNIEnv* jenv, jclass jLibClass, jstring jCode, jint jStart, jobject jGlobals, jobject jLocals) { + const char *codeChars; + jlong result; + codeChars = (*jenv)->GetStringUTFChars(jenv, jCode, NULL); if (codeChars == NULL) { - // todo: Throw out-of-memory error + PyLib_ThrowOOM(jenv); + return NULL; + } + + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_executeCode: code='%s'\n", codeChars); + + result = executeInternal(jenv, jLibClass, jStart, jGlobals, jLocals, (DoRun)pyRunStringWrapper, codeChars); + + if (codeChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jCode, codeChars); + } + + return result; +} + +typedef struct { + FILE *fp; + const char *filechars; +} RunFileArgs; + +PyObject *pyRunFileWrapper(RunFileArgs *args, int start, PyObject *globals, PyObject *locals) { + return PyRun_File(args->fp, args->filechars, start, globals, locals); +} + +/** + * Calls PyRun_Script under the covers to execute the script contents. + * + * jStart must be JPy_IM_STATEMENT, JPy_IM_SCRIPT, JPy_IM_EXPRESSION; matching what you are trying to + * run. If you use a statement or expression instead of a script; some of your code may be ignored. + * + * If jGlobals is not specified, then the main module globals are used. + * + * If jLocals is not specified, then the globals are used. + * + * jGlobals and jLocals may be a PyObject, in which case they are used without translation. Otherwise, + * they must be a map from String to Object, and will be copied to a new python dictionary. After execution + * completes the dictionary entries will be copied back. + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv* jenv, jclass jLibClass, jstring jFile, jint jStart, jobject jGlobals, jobject jLocals) { + RunFileArgs runFileArgs; + jlong result = 0; + + runFileArgs.fp = NULL; + runFileArgs.filechars = NULL; + + runFileArgs.filechars = (*jenv)->GetStringUTFChars(jenv, jFile, NULL); + if (runFileArgs.filechars == NULL) { + PyLib_ThrowOOM(jenv); goto error; } - pyGlobals = PyModule_GetDict(pyMainModule); // borrowed ref - if (pyGlobals == NULL) { - PyLib_HandlePythonException(jenv); + runFileArgs.fp = fopen(runFileArgs.filechars, "r"); + if (!runFileArgs.fp) { + PyLib_ThrowFNFE(jenv, runFileArgs.filechars); goto error; } - pyLocals = PyDict_New(); // new ref - if (pyLocals == NULL) { + result = executeInternal(jenv, jLibClass, jStart, jGlobals, jLocals, (DoRun)pyRunFileWrapper, &runFileArgs); + +error: + if (runFileArgs.filechars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jFile, runFileArgs.filechars); + } + if (runFileArgs.fp != NULL) { + fclose(runFileArgs.fp); + } + return result; +} + +/* + * Class: org_jpy_python_PyLib + * Method: incRef + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_incRef + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + Py_ssize_t refCount; + + pyObject = (PyObject*) objId; + + if (Py_IsInitialized()) { + JPy_BEGIN_GIL_STATE + + refCount = pyObject->ob_refcnt; + JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_incRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); + Py_INCREF(pyObject); + + JPy_END_GIL_STATE + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_incRef: error: no interpreter: pyObject=%p\n", pyObject); + } +} + +/* + * Class: org_jpy_python_PyLib + * Method: decRef + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + Py_ssize_t refCount; + + pyObject = (PyObject*) objId; + + if (Py_IsInitialized()) { + JPy_BEGIN_GIL_STATE + + refCount = pyObject->ob_refcnt; + if (refCount <= 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: refCount <= 0: pyObject=%p, refCount=%d\n", pyObject, refCount); + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_decRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); + Py_DECREF(pyObject); + } + + JPy_END_GIL_STATE + } else { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: no interpreter: pyObject=%p\n", pyObject); + } +} + + +/* + * Class: org_jpy_python_PyLib + * Method: getIntValue + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jint value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + value = (jint) JPy_AS_CLONG(pyObject); + + JPy_END_GIL_STATE + + return value; +} + +/** + * Used to convert a python object into it's corresponding boolean. If the PyObject is not a boolean; + * then return false. + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_getBooleanValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jboolean value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + if (PyBool_Check(pyObject)) { + value = (pyObject == Py_True) ? JNI_TRUE : JNI_FALSE; + } else { + value = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return value; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getDoubleValue + * Signature: (J)D + */ +JNIEXPORT jdouble JNICALL Java_org_jpy_PyLib_getDoubleValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jdouble value; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + value = (jdouble) PyFloat_AsDouble(pyObject); + + JPy_END_GIL_STATE + + return value; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getStringValue + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jstring jString; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + if (JPy_AsJString(jenv, pyObject, &jString) < 0) { + jString = NULL; + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getStringValue: error: failed to convert Python object to Java String\n"); PyLib_HandlePythonException(jenv); - goto error; } - // todo for https://github.com/bcdev/jpy/issues/53 - // - copy jGlobals into pyGlobals (convert Java --> Python values) - // - copy jLocals into pyLocals (convert Java --> Python values) + JPy_END_GIL_STATE + + return jString; +} + +/* + * Class: org_jpy_python_PyLib + * Method: getObjectValue + * Signature: (J)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jobject jObject; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + if (JObj_Check(pyObject)) { + jObject = ((JPy_JObj*) pyObject)->objectRef; + } else { + if (JPy_AsJObject(jenv, pyObject, &jObject, JNI_FALSE) < 0) { + jObject = NULL; + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectValue: error: failed to convert Python object to Java Object\n"); + PyLib_HandlePythonException(jenv); + } + } + + JPy_END_GIL_STATE + + return jObject; +} + +/** + * Returns true if this object can be converted from a Python object into a Java object (or primitive); + * if this returns false, when you fetch an Object from Python it will be a PyObject wrapper. + * + * objId is a pointer to a PyObject. + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + jboolean result; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + result = pyObject == Py_None || JObj_Check(pyObject) || PyBool_Check(pyObject) || + JPy_IS_CLONG(pyObject) || PyFloat_Check(pyObject) || JPy_IS_STR(pyObject) ? JNI_TRUE : JNI_FALSE; + + + JPy_END_GIL_STATE + + return result; +} + +/** + * Gets the Python type object of specified objId. + * + * objId is a pointer to a PyObject. + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + PyObject* pyObject; + + JPy_BEGIN_GIL_STATE + + pyObject = ((PyObject*) objId)->ob_type; + + JPy_END_GIL_STATE + + return (jlong)pyObject; +} + +/** + * Evaluate PyDict_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @return true if objId is a python dictionary + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyDict_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE + + return result; +} + +/** + * Evaluate PyList_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @return true if objId is a python list + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyList_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } + + JPy_END_GIL_STATE - start = jStart == JPy_IM_STATEMENT ? Py_single_input : - jStart == JPy_IM_SCRIPT ? Py_file_input : - Py_eval_input; + return result; +} - pyReturnValue = PyRun_String(codeChars, start, pyGlobals, pyLocals); - if (pyReturnValue == NULL) { - PyLib_HandlePythonException(jenv); - goto error; - } +/** + * Evaluate PyBool_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python boolean + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; - // todo for https://github.com/bcdev/jpy/issues/53 - // - copy pyGlobals into jGlobals (convert Python --> Java values) - // - copy pyLocals into jLocals (convert Python --> Java values) - //dumpDict("pyGlobals", pyGlobals); - //dumpDict("pyLocals", pyLocals); + JPy_BEGIN_GIL_STATE -error: - if (codeChars != NULL) { - (*jenv)->ReleaseStringUTFChars(jenv, jCode, codeChars); + if (PyBool_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; } - Py_XDECREF(pyLocals); - JPy_END_GIL_STATE - return (jlong) pyReturnValue; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: incRef - * Signature: (J)V +/** + * Check equality against Py_None and a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a None */ -JNIEXPORT void JNICALL Java_org_jpy_PyLib_incRef +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - Py_ssize_t refCount; + jboolean result; - pyObject = (PyObject*) objId; - - if (Py_IsInitialized()) { - JPy_BEGIN_GIL_STATE - - refCount = pyObject->ob_refcnt; - JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_incRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); - Py_INCREF(pyObject); + JPy_BEGIN_GIL_STATE - JPy_END_GIL_STATE + if (Py_None == (((PyObject*) objId))) { + result = JNI_TRUE; } else { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_incRef: error: no interpreter: pyObject=%p\n", pyObject); + result = JNI_FALSE; } + + JPy_END_GIL_STATE + + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: decRef - * Signature: (J)V +/** + * Evaluate PyInt_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python int */ -JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - Py_ssize_t refCount; + int check; - pyObject = (PyObject*) objId; + JPy_BEGIN_GIL_STATE - if (Py_IsInitialized()) { - JPy_BEGIN_GIL_STATE +#ifdef JPY_COMPAT_27 + check = PyInt_Check(((PyObject*) objId)); +#else + check = JPy_IS_CLONG(((PyObject*) objId)); +#endif - refCount = pyObject->ob_refcnt; - if (refCount <= 0) { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: refCount <= 0: pyObject=%p, refCount=%d\n", pyObject, refCount); - } else { - JPy_DIAG_PRINT(JPy_DIAG_F_MEM, "Java_org_jpy_PyLib_decRef: pyObject=%p, refCount=%d, type='%s'\n", pyObject, refCount, Py_TYPE(pyObject)->tp_name); - Py_DECREF(pyObject); - } + JPy_END_GIL_STATE - JPy_END_GIL_STATE + return check ? (jboolean)JNI_TRUE : (jboolean)JNI_FALSE; +} + +/** + * Evaluate PyLong_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python long + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck + (JNIEnv* jenv, jclass jLibClass, jlong objId) +{ + jboolean result; + + JPy_BEGIN_GIL_STATE + + if (PyLong_Check(((PyObject*) objId))) { + result = JNI_TRUE; } else { - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_decRef: error: no interpreter: pyObject=%p\n", pyObject); + result = JNI_FALSE; } -} + JPy_END_GIL_STATE -/* - * Class: org_jpy_python_PyLib - * Method: getIntValue - * Signature: (J)I + return result; +} + +/** + * Evaluate PyFloat_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python float */ -JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jint value; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; - value = (jint) JPy_AS_CLONG(pyObject); + if (PyFloat_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } JPy_END_GIL_STATE - return value; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getDoubleValue - * Signature: (J)D +/** + * Evaluate PyString_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python String */ -JNIEXPORT jdouble JNICALL Java_org_jpy_PyLib_getDoubleValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jdouble value; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; - value = (jdouble) PyFloat_AsDouble(pyObject); + if (JPy_IS_STR(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } JPy_END_GIL_STATE - return value; + return result; } -/* - * Class: org_jpy_python_PyLib - * Method: getStringValue - * Signature: (J)Ljava/lang/String; +/** + * Evaluate PyCallable_Check against a Python object. + * + * @param jenv JNI environment. + * @param jLibClass + * @param objId a pointer to a python object + * @return true if objId is a python callable */ -JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck (JNIEnv* jenv, jclass jLibClass, jlong objId) { - PyObject* pyObject; - jstring jString; + jboolean result; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; + if (PyCallable_Check(((PyObject*) objId))) { + result = JNI_TRUE; + } else { + result = JNI_FALSE; + } - if (JPy_AsJString(jenv, pyObject, &jString) < 0) { - jString = NULL; - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getStringValue: error: failed to convert Python object to Java String\n"); - PyLib_HandlePythonException(jenv); + JPy_END_GIL_STATE + + return result; +} + +/** + * Runs the str function on a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the class object for PyLib + * @param objId a pointer to a python object + * @return the Python toString of this object + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; + jobject jObject; + PyObject *pyStr; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject *) objId; + + pyStr = PyObject_Str(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); + Py_DECREF(pyStr); + } else { + jObject = NULL; } + JPy_END_GIL_STATE - return jString; + return jObject; } -/* - * Class: org_jpy_python_PyLib - * Method: getObjectValue - * Signature: (J)Ljava/lang/Object; + +/** + * Runs the repr function on a Python object. + * + * @param jenv JNI environment. + * @param jLibClass the class object for PyLib + * @param objId a pointer to a python object + * @return the Python representation string of this object */ -JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue - (JNIEnv* jenv, jclass jLibClass, jlong objId) -{ - PyObject* pyObject; +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv* jenv, jclass jLibClass, jlong objId) { + PyObject *pyObject; jobject jObject; + PyObject *pyStr; JPy_BEGIN_GIL_STATE - pyObject = (PyObject*) objId; + pyObject = (PyObject *) objId; - if (JObj_Check(pyObject)) { - jObject = ((JPy_JObj*) pyObject)->objectRef; + pyStr = PyObject_Repr(pyObject); + if (pyStr) { + jObject = (*jenv)->NewStringUTF(jenv, JPy_AS_UTF8(pyStr)); + Py_DECREF(pyStr); } else { - if (JPy_AsJObject(jenv, pyObject, &jObject) < 0) { - jObject = NULL; - JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectValue: error: failed to convert Python object to Java Object\n"); - PyLib_HandlePythonException(jenv); - } + jObject = NULL; } + JPy_END_GIL_STATE return jObject; @@ -604,7 +1414,7 @@ JNIEXPORT jobjectArray JNICALL Java_org_jpy_PyLib_getObjectArrayValue jObject = NULL; goto error; } - if (JPy_AsJObject(jenv, pyItem, &jItem) < 0) { + if (JPy_AsJObject(jenv, pyItem, &jItem, JNI_FALSE) < 0) { (*jenv)->DeleteLocalRef(jenv, jObject); jObject = NULL; JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_getObjectArrayValue: error: failed to convert Python item to Java Object\n"); @@ -640,12 +1450,16 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_importModule (JNIEnv* jenv, jclass jLibClass, jstring jName) { PyObject* pyName; - PyObject* pyModule; + PyObject* pyModule = NULL; const char* nameChars; JPy_BEGIN_GIL_STATE nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_importModule: name='%s'\n", nameChars); /* Note: pyName is a new reference */ pyName = JPy_FROM_CSTR(nameChars); @@ -655,7 +1469,11 @@ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_importModule PyLib_HandlePythonException(jenv); } Py_XDECREF(pyName); - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } JPy_END_GIL_STATE @@ -739,6 +1557,10 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue pyObject = (PyObject*) objId; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_setAttributeValue: objId=%p, name='%s', jValue=%p, jValueClass=%p\n", pyObject, nameChars, jValue, jValueClass); if (jValueClass != NULL) { @@ -766,9 +1588,92 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue } error: - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } + + JPy_END_GIL_STATE +} + +/** + * Deletes an attribute from an object. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @param jName the java string naming the attribute to delete + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute + (JNIEnv* jenv, jclass jLibClass, jlong objId, jstring jName) +{ + PyObject* pyObject; + const char* nameChars; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_delAttribute: objId=%p, name='%s'\n", pyObject, nameChars); + + if (PyObject_DelAttrString(pyObject, nameChars) < 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "Java_org_jpy_PyLib_delAttribute: error: PyObject_DelAttrString failed on attribute '%s'\n", nameChars); + PyLib_HandlePythonException(jenv); + goto error; + } + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } + + + JPy_END_GIL_STATE +} + +/* + * Checks for an attribute's existence. + * + * @param jenv JNI environment. + * @param jLibClass the PyLib class object + * @param objId a pointer to a python object + * @param jName the java string naming the attribute to delete + * + * @return true if the attribute exists on this object + */ + +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute + (JNIEnv* jenv, jclass jLibClass, jlong objId, jstring jName) +{ + PyObject* pyObject; + const char* nameChars; + jboolean result = JNI_FALSE; + + JPy_BEGIN_GIL_STATE + + pyObject = (PyObject*) objId; + + nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "Java_org_jpy_PyLib_delAttribute: objId=%p, name='%s'\n", pyObject, nameChars); + + result = PyObject_HasAttrString(pyObject, nameChars) ? JNI_TRUE : JNI_FALSE; + +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } JPy_END_GIL_STATE + + return result; } @@ -859,10 +1764,14 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_00024Diag_setFlags PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyObject, jstring jName) { - PyObject* pyValue; + PyObject* pyValue = NULL; const char* nameChars; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "PyLib_GetAttributeObject: objId=%p, name='%s'\n", pyObject, nameChars); /* Note: pyValue is a new reference */ pyValue = PyObject_GetAttrString(pyObject, nameChars); @@ -870,16 +1779,19 @@ PyObject* PyLib_GetAttributeObject(JNIEnv* jenv, PyObject* pyObject, jstring jNa JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "PyLib_GetAttributeObject: error: attribute not found '%s'\n", nameChars); PyLib_HandlePythonException(jenv); } - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); +error: + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } return pyValue; } PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean isMethodCall, jstring jName, jint argCount, jobjectArray jArgs, jobjectArray jParamClasses) { - PyObject* pyCallable; - PyObject* pyArgs; + PyObject* pyCallable = NULL; + PyObject* pyArgs = NULL; PyObject* pyArg; - PyObject* pyReturnValue; + PyObject* pyReturnValue = Py_None; const char* nameChars; jint i; jobject jArg; @@ -889,6 +1801,10 @@ PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean i pyReturnValue = NULL; nameChars = (*jenv)->GetStringUTFChars(jenv, jName, NULL); + if (nameChars == NULL) { + PyLib_ThrowOOM(jenv); + goto error; + } JPy_DIAG_PRINT(JPy_DIAG_F_EXEC, "PyLib_CallAndReturnObject: objId=%p, isMethodCall=%d, name='%s', argCount=%d\n", pyObject, isMethodCall, nameChars, argCount); @@ -981,7 +1897,9 @@ PyObject* PyLib_CallAndReturnObject(JNIEnv *jenv, PyObject* pyObject, jboolean i Py_INCREF(pyReturnValue); error: - (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + if (nameChars != NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, jName, nameChars); + } Py_XDECREF(pyCallable); Py_XDECREF(pyArgs); @@ -1051,6 +1969,8 @@ void PyLib_HandlePythonException(JNIEnv* jenv) char* filenameChars = NULL; char* namespaceChars = NULL; + jclass jExceptionClass; + if (PyErr_Occurred() == NULL) { return; } @@ -1064,6 +1984,13 @@ void PyLib_HandlePythonException(JNIEnv* jenv) typeChars = PyLib_ObjToChars(pyType, &pyTypeUtf8); valueChars = PyLib_ObjToChars(pyValue, &pyValueUtf8); + if (PyObject_TypeCheck(pyValue, (PyTypeObject*) PyExc_KeyError)) { + jExceptionClass = JPy_KeyError_JClass; + } else if (PyObject_TypeCheck(pyValue, (PyTypeObject*) PyExc_StopIteration)) { + jExceptionClass = JPy_StopIteration_JClass; + } else { + jExceptionClass = JPy_RuntimeException_JClass; + } if (pyTraceback != NULL) { PyObject* pyFrame = NULL; @@ -1106,13 +2033,13 @@ void PyLib_HandlePythonException(JNIEnv* jenv) linenoChars != NULL ? linenoChars : JPY_NOT_AVAILABLE_MSG, namespaceChars != NULL ? namespaceChars : JPY_NOT_AVAILABLE_MSG, filenameChars != NULL ? filenameChars : JPY_NOT_AVAILABLE_MSG); - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, javaMessage); + (*jenv)->ThrowNew(jenv, jExceptionClass, javaMessage); PyMem_Del(javaMessage); } else { - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, JPY_INFO_ALLOC_FAILED_MSG); + (*jenv)->ThrowNew(jenv, jExceptionClass, JPY_INFO_ALLOC_FAILED_MSG); } } else { - (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, JPY_NO_INFO_MSG); + (*jenv)->ThrowNew(jenv, jExceptionClass, JPY_NO_INFO_MSG); } Py_XDECREF(pyType); @@ -1127,6 +2054,40 @@ void PyLib_HandlePythonException(JNIEnv* jenv) PyErr_Clear(); } +/** + * Throw an OutOfMemoryError. + * @param jenv the jni environment + */ +void PyLib_ThrowOOM(JNIEnv* jenv) { + (*jenv)->ThrowNew(jenv, JPy_OutOfMemoryError_JClass, "Out of memory"); +} + +/** + * Throw a FileNotFoundException. + * @param jenv the jni environment + */ +void PyLib_ThrowFNFE(JNIEnv* jenv, const char *file) { + (*jenv)->ThrowNew(jenv, JPy_FileNotFoundException_JClass, file); +} + +/** + * Throw an UnsupportedOperationException. + * @param jenv the jni environment + * @param message the exception message + */ +void PyLib_ThrowUOE(JNIEnv* jenv, const char *message) { + (*jenv)->ThrowNew(jenv, JPy_UnsupportedOperationException_JClass, message); +} + +/** + * Throw an UnsupportedOperationException. + * @param jenv the jni environment + * @param message the exception message + */ +void PyLib_ThrowRTE(JNIEnv* jenv, const char *message) { + (*jenv)->ThrowNew(jenv, JPy_RuntimeException_JClass, message); +} + //////////////////////////////////////////////////////////////////////////////////////////////// // Redirect stdout diff --git a/src/main/c/jni/org_jpy_PyLib.h b/src/main/c/jni/org_jpy_PyLib.h index 33e40bbf7b..6d870fd15f 100644 --- a/src/main/c/jni/org_jpy_PyLib.h +++ b/src/main/c/jni/org_jpy_PyLib.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isPythonRunning (JNIEnv *, jclass); +/* + * Class: org_jpy_PyLib + * Method: setPythonHome + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jint JNICALL Java_org_jpy_PyLib_setPythonHome + (JNIEnv* jenv, jclass jLibClass, jstring jPythonHome); + /* * Class: org_jpy_PyLib * Method: startPython0 @@ -49,12 +57,36 @@ JNIEXPORT jint JNICALL Java_org_jpy_PyLib_execScript /* * Class: org_jpy_PyLib - * Method: execute - * Signature: (Ljava/lang/String;ILjava/util/Map;Ljava/util/Map;)J + * Method: executeCode + * Signature: (Ljava/lang/String;ILjava/lang/Object;Ljava/lang/Object;)J */ JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeCode (JNIEnv *, jclass, jstring, jint, jobject, jobject); +/* + * Class: org_jpy_PyLib + * Method: executeScript + * Signature: (Ljava/lang/String;ILjava/lang/Object;Ljava/lang/Object;)J + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_executeScript + (JNIEnv *, jclass, jstring, jint, jobject, jobject); + +/* + * Class: org_jpy_PyLib + * Method: getMainGlobals + * Signature: ()Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getMainGlobals + (JNIEnv *, jclass); + +/* + * Class: org_jpy_PyLib + * Method: copyDict + * Signature: (J)Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_copyDict + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: incRef @@ -79,6 +111,14 @@ JNIEXPORT void JNICALL Java_org_jpy_PyLib_decRef JNIEXPORT jint JNICALL Java_org_jpy_PyLib_getIntValue (JNIEnv *, jclass, jlong); +/* + * Class: org_jpy_PyLib + * Method: getBooleanValue + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_getBooleanValue + (JNIEnv *, jclass, jlong); + /* * Class: org_jpy_PyLib * Method: getDoubleValue @@ -103,6 +143,118 @@ JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_getStringValue JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getObjectValue (JNIEnv *, jclass, jlong); +/* + * Class: org_jpy_PyLib + * Method: isConvertible + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_isConvertible + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyNoneCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyNoneCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyDictCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyDictCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyListCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyListCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyBoolCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyBoolCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyIntCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyIntCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyLongCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyLongCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyFloatCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyFloatCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyStringCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyStringCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: pyCallableCheck + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_pyCallableCheck + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: getType + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_org_jpy_PyLib_getType + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: str + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_str + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: repr + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jpy_PyLib_repr + (JNIEnv *, jclass, jlong); + +/* + * Class: org_jpy_PyLib + * Method: newDict + * Signature: ()Lorg/jpy/PyObject; + */ +JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_newDict + (JNIEnv *, jclass); + /* * Class: org_jpy_PyLib * Method: getObjectArrayValue @@ -143,6 +295,22 @@ JNIEXPORT jobject JNICALL Java_org_jpy_PyLib_getAttributeValue JNIEXPORT void JNICALL Java_org_jpy_PyLib_setAttributeValue (JNIEnv *, jclass, jlong, jstring, jobject, jclass); +/* + * Class: org_jpy_PyLib + * Method: delAttribute + * Signature: (JLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_org_jpy_PyLib_delAttribute + (JNIEnv *, jclass, jlong, jstring); + +/* + * Class: org_jpy_PyLib + * Method: hasAttribute + * Signature: (JLjava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_jpy_PyLib_hasAttribute + (JNIEnv *, jclass, jlong, jstring); + /* * Class: org_jpy_PyLib * Method: callAndReturnObject diff --git a/src/main/c/jpy_compat.h b/src/main/c/jpy_compat.h index 818fb21447..62f969bc88 100644 --- a/src/main/c/jpy_compat.h +++ b/src/main/c/jpy_compat.h @@ -30,6 +30,9 @@ extern "C" { #undef JPY_COMPAT_33P #elif PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 3 #define JPY_COMPAT_33P 1 +#if PY_MINOR_VERSION >= 5 +#define JPY_COMPAT_35P 1 +#endif #undef JPY_COMPAT_27 #else #error JPY_VERSION_ERROR @@ -79,4 +82,4 @@ wchar_t* JPy_AsWideCharString_PriorToPy33(PyObject *unicode, Py_ssize_t *size); #ifdef __cplusplus } /* extern "C" */ #endif -#endif /* !JPY_COMPAT_H */ \ No newline at end of file +#endif /* !JPY_COMPAT_H */ diff --git a/src/main/c/jpy_conv.c b/src/main/c/jpy_conv.c index 18880ad55d..f76c352c95 100644 --- a/src/main/c/jpy_conv.c +++ b/src/main/c/jpy_conv.c @@ -23,14 +23,14 @@ -int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef) +int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jboolean allowJavaWrapping) { - return JType_ConvertPythonToJavaObject(jenv, JPy_JObject, pyObj, objectRef); + return JType_ConvertPythonToJavaObject(jenv, JPy_JObject, pyObj, objectRef, allowJavaWrapping); } int JPy_AsJObjectWithType(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, JPy_JType* type) { - return JType_ConvertPythonToJavaObject(jenv, type, pyObj, objectRef); + return JType_ConvertPythonToJavaObject(jenv, type, pyObj, objectRef, JNI_FALSE); } int JPy_AsJObjectWithClass(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jclass classRef) @@ -52,7 +52,7 @@ int JPy_AsJObjectWithClass(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jc return -1; } } else { - if (JPy_AsJObject(jenv, pyObj, objectRef) < 0) { + if (JPy_AsJObject(jenv, pyObj, objectRef, JNI_FALSE) < 0) { return -1; } } diff --git a/src/main/c/jpy_conv.h b/src/main/c/jpy_conv.h index 7a9696737b..f1dfab64db 100644 --- a/src/main/c/jpy_conv.h +++ b/src/main/c/jpy_conv.h @@ -86,8 +86,10 @@ int JPy_AsJString(JNIEnv* jenv, PyObject* pyObj, jstring* stringRef); /** * Convert any Python objects to Java object. + * + * @param allowObjectWrapping if true, may return a PyObject for unrecognized object types */ -int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef); +int JPy_AsJObject(JNIEnv* jenv, PyObject* pyObj, jobject* objectRef, jboolean allowObjectWrapping); /** * Convert Python objects to Java object with known type. diff --git a/src/main/c/jpy_jmethod.c b/src/main/c/jpy_jmethod.c index 050248c0ed..3e6e05cc5d 100644 --- a/src/main/c/jpy_jmethod.c +++ b/src/main/c/jpy_jmethod.c @@ -12,6 +12,8 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. */ #include "jpy_module.h" @@ -29,6 +31,7 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, JPy_ParamDescriptor* paramDescriptors, JPy_ReturnDescriptor* returnDescriptor, jboolean isStatic, + jboolean isVarArgs, jmethodID mid) { PyTypeObject* type = &JMethod_Type; @@ -41,6 +44,7 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, method->paramDescriptors = paramDescriptors; method->returnDescriptor = returnDescriptor; method->isStatic = isStatic; + method->isVarArgs = isVarArgs; method->mid = mid; Py_INCREF(declaringClass); @@ -84,8 +88,11 @@ void JMethod_Del(JPy_JMethod* method) * Matches the give Python argument tuple against the Java method's formal parameters. * Returns the sum of the i-th argument against the i-th Java parameter. * The maximum match value returned is 100 * method->paramCount. + * + * The isVarArgsArray pointer is set to 1 if this is a varargs match for an object array + * argument. */ -int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* method, int argCount, PyObject* pyArgs) +int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* method, int argCount, PyObject* pyArgs, int *isVarArgArray) { JPy_ParamDescriptor* paramDescriptor; PyObject* pyArg; @@ -93,30 +100,53 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me int matchValue; int i; int i0; + int iLast; + *isVarArgArray = 0; if (method->isStatic) { - //printf("Static! method->paramCount=%d, argCount=%d\n", method->paramCount, argCount); - if (method->paramCount != argCount) { - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); - // argument count mismatch - return 0; - } - if (method->paramCount == 0) { - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: no-argument static method (matchValue=100)\n"); - // There can't be any other static method overloads with no parameters - return 100; - } + if (method->isVarArgs) { + if(argCount < method->paramCount - 1) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: var args argument count mismatch java=%d, python=%d (matchValue=0)\n", method->paramCount, argCount); + // argument count mismatch + return 0; + } + iLast = method->paramCount - 1; + } else { + if (method->paramCount != argCount) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); + // argument count mismatch + return 0; + } + if (method->paramCount == 0) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: no-argument static method (matchValue=100)\n"); + // There can't be any other static method overloads with no parameters + return 100; + } - i0 = 0; + iLast = argCount; + } matchValueSum = 0; + i0 = 0; } else { PyObject* self; - //printf("Non-Static! method->paramCount=%d, argCount=%d\n", method->paramCount, argCount); - if (method->paramCount != argCount - 1) { + //printf("Non-Static! method->paramCount=%d, argCount=%d, isVarArg=%d\n", method->paramCount, argCount, method->isVarArgs); + + if (method->isVarArgs) { + if (argCount < method->paramCount) { + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: var args argument count mismatch java=%d, python=%d (matchValue=0)\n", method->paramCount, argCount); + // argument count mismatch + return 0; + } + iLast = method->paramCount; + } + else if (method->paramCount != argCount - 1) { JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: argument count mismatch (matchValue=0)\n"); // argument count mismatch return 0; + } else { + iLast = method->paramCount + 1; } + self = PyTuple_GetItem(pyArgs, 0); if (self == Py_None) { JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: self argument is None (matchValue=0)\n"); @@ -141,8 +171,7 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me } paramDescriptor = method->paramDescriptors; - for (i = i0; i < argCount; i++) { - + for (i = i0; i < iLast; i++) { pyArg = PyTuple_GetItem(pyArgs, i); matchValue = paramDescriptor->MatchPyArg(jenv, paramDescriptor, pyArg); @@ -157,6 +186,34 @@ int JMethod_MatchPyArgs(JNIEnv* jenv, JPy_JType* declaringClass, JPy_JMethod* me matchValueSum += matchValue; paramDescriptor++; } + if (method->isVarArgs) { + int singleMatchValue = 0; + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, i=%d\n", argCount, i); + + if (argCount - i == 0) { + matchValueSum += 10; + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, matchValueSum=%d\n", argCount, method->paramCount, matchValueSum); + } else if (argCount - i == 1) { + // if we have exactly one argument, which matches our array type, then we can use that as an array + pyArg = PyTuple_GetItem(pyArgs, i); + singleMatchValue = paramDescriptor->MatchPyArg(jenv, paramDescriptor, pyArg); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, starting singleMatchValue=%d\n", argCount, method->paramCount, singleMatchValue); + } + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, argCount = %d, paramCount = %d, starting matchValue=%d\n", argCount, method->paramCount, matchValueSum); + matchValue = paramDescriptor->MatchVarArgPyArg(jenv, paramDescriptor, pyArgs, i); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JMethod_MatchPyArgs: isVarArgs, paramDescriptor->type->javaName='%s', matchValue=%d\n", paramDescriptor->type->javaName, matchValue); + if (matchValue == 0 && singleMatchValue == 0) { + return 0; + } + if (matchValue > singleMatchValue) { + matchValueSum += matchValue; + } else { + matchValueSum += singleMatchValue; + *isVarArgArray = 1; + } + } //printf("JMethod_MatchPyArgs 7\n"); return matchValueSum; @@ -185,7 +242,7 @@ PyObject* JMethod_FromJObject(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArg /** * Invoke a method. We have already ensured that the Python arguments and expected Java parameters match. */ -PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs) +PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, int isVarArgsArray) { jvalue* jArgs; JPy_ArgDisposer* argDisposers; @@ -195,7 +252,7 @@ PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyAr jclass classRef; //printf("JMethod_InvokeMethod 1: typeCode=%c\n", typeCode); - if (JMethod_CreateJArgs(jenv, method, pyArgs, &jArgs, &argDisposers) < 0) { + if (JMethod_CreateJArgs(jenv, method, pyArgs, &jArgs, &argDisposers, isVarArgsArray) < 0) { return NULL; } @@ -325,10 +382,11 @@ PyObject* JMethod_InvokeMethod(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyAr return returnValue; } -int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jvalue** argValuesRet, JPy_ArgDisposer** argDisposersRet) +int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jvalue** argValuesRet, JPy_ArgDisposer** argDisposersRet, int isVarArgsArray) { JPy_ParamDescriptor* paramDescriptor; - int i, i0, argCount; + Py_ssize_t i, i0, iLast; + Py_ssize_t argCount; PyObject* pyArg; jvalue* jValue; jvalue* jValues; @@ -343,10 +401,17 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva argCount = PyTuple_Size(pyArgs); - i0 = argCount - method->paramCount; - if (!(i0 == 0 || i0 == 1)) { - PyErr_SetString(PyExc_RuntimeError, "internal error"); - return -1; + if (method->isVarArgs) { + // need to know if we expect a self parameter + i0 = method->isStatic ? 0 : 1; + iLast = method->isStatic ? method->paramCount - 1 : method->paramCount; + } else { + i0 = argCount - method->paramCount; + if (!(i0 == 0 || i0 == 1)) { + PyErr_SetString(PyExc_RuntimeError, "internal error"); + return -1; + } + iLast = argCount; } jValues = PyMem_New(jvalue, method->paramCount); @@ -365,7 +430,7 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva paramDescriptor = method->paramDescriptors; jValue = jValues; argDisposer = argDisposers; - for (i = i0; i < argCount; i++) { + for (i = i0; i < iLast; i++) { pyArg = PyTuple_GetItem(pyArgs, i); jValue->l = 0; argDisposer->data = NULL; @@ -379,6 +444,28 @@ int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* method, PyObject* pyArgs, jva jValue++; argDisposer++; } + if (method->isVarArgs) { + if (isVarArgsArray) { + pyArg = PyTuple_GetItem(pyArgs, i); + jValue->l = 0; + argDisposer->data = NULL; + argDisposer->DisposeArg = NULL; + if (paramDescriptor->ConvertPyArg(jenv, paramDescriptor, pyArg, jValue, argDisposer) < 0) { + PyMem_Del(jValues); + PyMem_Del(argDisposers); + return -1; + } + } else { + jValue->l = 0; + argDisposer->data = NULL; + argDisposer->DisposeArg = NULL; + if (paramDescriptor->ConvertVarArgPyArg(jenv, paramDescriptor, pyArgs, i, jValue, argDisposer) < 0) { + PyMem_Del(jValues); + PyMem_Del(argDisposers); + return -1; + } + } + } *argValuesRet = jValues; *argDisposersRet = argDisposers; @@ -611,19 +698,22 @@ typedef struct JPy_MethodFindResult JPy_JMethod* method; int matchValue; int matchCount; + int isVarArgsArray; } JPy_MethodFindResult; JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, JPy_MethodFindResult* result) { - int overloadCount; - int argCount; + Py_ssize_t overloadCount; + Py_ssize_t argCount; int matchCount; int matchValue; int matchValueMax; JPy_JMethod* currMethod; JPy_JMethod* bestMethod; int i; + int currentIsVarArgsArray; + int bestIsVarArgsArray; result->method = NULL; result->matchValue = 0; @@ -639,26 +729,34 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* matchCount = 0; matchValueMax = -1; bestMethod = NULL; + bestIsVarArgsArray = 0; - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: method '%s#%s': overloadCount=%d\n", - overloadedMethod->declaringClass->javaName, JPy_AS_UTF8(overloadedMethod->name), overloadCount); + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: method '%s#%s': overloadCount=%d, argCount=%d\n", + overloadedMethod->declaringClass->javaName, JPy_AS_UTF8(overloadedMethod->name), overloadCount, argCount); for (i = 0; i < overloadCount; i++) { currMethod = (JPy_JMethod*) PyList_GetItem(overloadedMethod->methodList, i); - matchValue = JMethod_MatchPyArgs(jenv, overloadedMethod->declaringClass, currMethod, argCount, pyArgs); - JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: methodList[%d]: paramCount=%d, matchValue=%d\n", i, - currMethod->paramCount, matchValue); + if (currMethod->isVarArgs && matchValueMax > 0 && !bestMethod->isVarArgs) { + // we should not process varargs if we have already found a suitable fixed arity method + break; + } + + matchValue = JMethod_MatchPyArgs(jenv, overloadedMethod->declaringClass, currMethod, argCount, pyArgs, ¤tIsVarArgsArray); + + JPy_DIAG_PRINT(JPy_DIAG_F_METH, "JOverloadedMethod_FindMethod0: methodList[%d]: paramCount=%d, matchValue=%d, isVarArgs=%d\n", i, + currMethod->paramCount, matchValue, currMethod->isVarArgs); if (matchValue > 0) { if (matchValue > matchValueMax) { matchValueMax = matchValue; bestMethod = currMethod; matchCount = 1; + bestIsVarArgsArray = currentIsVarArgsArray; } else if (matchValue == matchValueMax) { matchCount++; } - if (matchValue >= 100 * argCount) { + if (!currMethod->isVarArgs && (matchValue >= 100 * argCount)) { // we can't get any better (if so, we have an internal problem) break; } @@ -668,16 +766,18 @@ JPy_JMethod* JOverloadedMethod_FindMethod0(JNIEnv* jenv, JPy_JOverloadedMethod* if (bestMethod == NULL) { matchValueMax = 0; matchCount = 0; + bestIsVarArgsArray = 0; } result->method = bestMethod; result->matchValue = matchValueMax; result->matchCount = matchCount; + result->isVarArgsArray = bestIsVarArgsArray; return bestMethod; } -JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, jboolean visitSuperClass) +JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* pyArgs, jboolean visitSuperClass, int *isVarArgsArray) { JPy_JOverloadedMethod* currentOM; JPy_MethodFindResult result; @@ -700,6 +800,7 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o bestResult.method = NULL; bestResult.matchValue = 0; bestResult.matchCount = 0; + bestResult.isVarArgsArray = 0; currentOM = overloadedMethod; while (1) { @@ -708,8 +809,11 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o return NULL; } if (result.method != NULL) { - if (result.matchValue >= 100 * argCount) { + // in the case where we have a match count that is perfect, but more than one match; the super class might + // have a better match count, because varargs can have fewer arguments than actual parameters. + if (result.matchValue >= 100 * argCount && result.matchCount == 1) { // We can't get any better. + *isVarArgsArray = result.isVarArgsArray; return result.method; } else if (result.matchValue > 0 && result.matchValue > bestResult.matchValue) { // We may have better matching methods overloads in the super class (if any) @@ -740,6 +844,7 @@ JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* o PyErr_SetString(PyExc_RuntimeError, "ambiguous Java method call, too many matching method overloads found"); return NULL; } else { + *isVarArgsArray = bestResult.isVarArgsArray; return bestResult.method; } } else { @@ -774,7 +879,27 @@ JPy_JOverloadedMethod* JOverloadedMethod_New(JPy_JType* declaringClass, PyObject int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMethod* method) { - return PyList_Append(overloadedMethod->methodList, (PyObject*) method); + Py_ssize_t destinationIndex = -1; + + if (!method->isVarArgs) { + Py_ssize_t ii; + // we need to insert this before the first varargs method + Py_ssize_t size = PyList_Size(overloadedMethod->methodList); + for (ii = 0; ii < size; ii++) { + PyObject *check = PyList_GetItem(overloadedMethod->methodList, ii); + if (((JPy_JMethod *) check)->isVarArgs) { + // this is the first varargs method, so we should go before it + destinationIndex = ii; + break; + } + } + } + + if (destinationIndex >= 0) { + return PyList_Insert(overloadedMethod->methodList, destinationIndex, (PyObject *) method); + } else { + return PyList_Append(overloadedMethod->methodList, (PyObject *) method); + } } /** @@ -795,15 +920,16 @@ PyObject* JOverloadedMethod_call(JPy_JOverloadedMethod* self, PyObject *args, Py { JNIEnv* jenv; JPy_JMethod* method; + int isVarArgsArray; JPy_GET_JNI_ENV_OR_RETURN(jenv, NULL) - method = JOverloadedMethod_FindMethod(jenv, self, args, JNI_TRUE); + method = JOverloadedMethod_FindMethod(jenv, self, args, JNI_TRUE, &isVarArgsArray); if (method == NULL) { return NULL; } - return JMethod_InvokeMethod(jenv, method, args); + return JMethod_InvokeMethod(jenv, method, args, isVarArgsArray); } /** diff --git a/src/main/c/jpy_jmethod.h b/src/main/c/jpy_jmethod.h index 3bcea12e19..8661f07e1b 100644 --- a/src/main/c/jpy_jmethod.h +++ b/src/main/c/jpy_jmethod.h @@ -12,6 +12,8 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. */ #ifndef JPY_JMETHOD_H @@ -38,6 +40,8 @@ typedef struct int paramCount; // Method is static? char isStatic; + // Method is varargs? + char isVarArgs; // Method parameter types. Will be NULL, if parameter_count == 0. JPy_ParamDescriptor* paramDescriptors; // Method return type. Will be NULL for constructors. @@ -73,7 +77,7 @@ JPy_JOverloadedMethod; */ extern PyTypeObject JOverloadedMethod_Type; -JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple, jboolean visitSuperClass); +JPy_JMethod* JOverloadedMethod_FindMethod(JNIEnv* jenv, JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple, jboolean visitSuperClass, int *isVarArgsArray); JPy_JMethod* JOverloadedMethod_FindStaticMethod(JPy_JOverloadedMethod* overloadedMethod, PyObject* argTuple); JPy_JOverloadedMethod* JOverloadedMethod_New(JPy_JType* declaringClass, PyObject* name, JPy_JMethod* method); int JOverloadedMethod_AddMethod(JPy_JOverloadedMethod* overloadedMethod, JPy_JMethod* method); @@ -84,13 +88,14 @@ JPy_JMethod* JMethod_New(JPy_JType* declaringClass, JPy_ParamDescriptor* paramDescriptors, JPy_ReturnDescriptor* returnDescriptor, jboolean isStatic, + jboolean isVarArgs, jmethodID mid); void JMethod_Del(JPy_JMethod* method); int JMethod_ConvertToJavaValues(JNIEnv* jenv, JPy_JMethod* jMethod, int argCount, PyObject* argTuple, jvalue* jArgs); -int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* jMethod, PyObject* argTuple, jvalue** jValues, JPy_ArgDisposer** jDisposers); +int JMethod_CreateJArgs(JNIEnv* jenv, JPy_JMethod* jMethod, PyObject* argTuple, jvalue** jValues, JPy_ArgDisposer** jDisposers, int isVarArgsArray); void JMethod_DisposeJArgs(JNIEnv* jenv, int paramCount, jvalue* jValues, JPy_ArgDisposer* jDisposers); #ifdef __cplusplus diff --git a/src/main/c/jpy_jobj.c b/src/main/c/jpy_jobj.c index a9fda7489c..ab1bd6ca6b 100644 --- a/src/main/c/jpy_jobj.c +++ b/src/main/c/jpy_jobj.c @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" @@ -23,7 +26,7 @@ #include "jpy_jfield.h" #include "jpy_conv.h" -JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef) +PyObject* JObj_New(JNIEnv* jenv, jobject objectRef) { jclass classRef; JPy_JType* type; @@ -38,8 +41,11 @@ JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef) return JObj_FromType(jenv, type, objectRef); } -JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) +PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) { + PyObject* callable; + PyObject* callableResult; + JPy_JObj* obj; obj = (JPy_JObj*) PyObject_New(JPy_JObj, (PyTypeObject*) type); @@ -63,7 +69,21 @@ JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef) array->bufferExportCount = 0; } - return obj; + // we check the type translations dictionary for a callable for this java type name, + // and apply the returned callable to the wrapped object + callable = PyDict_GetItemString(JPy_Type_Translations, type->javaName); + if (callable != NULL) { + if (PyCallable_Check(callable)) { + callableResult = PyObject_CallFunction(callable, "OO", type, obj); + if (callableResult == NULL) { + return Py_None; + } else { + return callableResult; + } + } + } + + return (PyObject *)obj; } /** @@ -79,6 +99,7 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) jobject objectRef; jvalue* jArgs; JPy_ArgDisposer* jDisposers; + int isVarArgsArray; JPy_GET_JNI_ENV_OR_RETURN(jenv, -1) @@ -101,12 +122,12 @@ int JObj_init(JPy_JObj* self, PyObject* args, PyObject* kwds) return -1; } - jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE); + jMethod = JOverloadedMethod_FindMethod(jenv, (JPy_JOverloadedMethod*) constructor, args, JNI_FALSE, &isVarArgsArray); if (jMethod == NULL) { return -1; } - if (JMethod_CreateJArgs(jenv, jMethod, args, &jArgs, &jDisposers) < 0) { + if (JMethod_CreateJArgs(jenv, jMethod, args, &jArgs, &jDisposers, isVarArgsArray) < 0) { return -1; } diff --git a/src/main/c/jpy_jobj.h b/src/main/c/jpy_jobj.h index c7672e757e..e222bee77c 100644 --- a/src/main/c/jpy_jobj.h +++ b/src/main/c/jpy_jobj.h @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #ifndef JPY_JOBJ_H @@ -37,8 +40,8 @@ JPy_JObj; int JObj_Check(PyObject* arg); -JPy_JObj* JObj_New(JNIEnv* jenv, jobject objectRef); -JPy_JObj* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef); +PyObject* JObj_New(JNIEnv* jenv, jobject objectRef); +PyObject* JObj_FromType(JNIEnv* jenv, JPy_JType* type, jobject objectRef); int JObj_InitTypeSlots(PyTypeObject* type, const char* typeName, PyTypeObject* superType); diff --git a/src/main/c/jpy_jtype.c b/src/main/c/jpy_jtype.c index 339c897762..78f5e27208 100644 --- a/src/main/c/jpy_jtype.c +++ b/src/main/c/jpy_jtype.c @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" @@ -34,7 +37,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type); int JType_AddMethod(JPy_JType* type, JPy_JMethod* method); JPy_ReturnDescriptor* JType_CreateReturnDescriptor(JNIEnv* jenv, jclass returnType); JPy_ParamDescriptor* JType_CreateParamDescriptors(JNIEnv* jenv, int paramCount, jarray paramTypes); -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor); +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean isLastVarArg); void JType_InitMethodParamDescriptorFunctions(JPy_JType* type, JPy_JMethod* method); int JType_ProcessField(JNIEnv* jenv, JPy_JType* declaringType, PyObject* fieldKey, const char* fieldName, jclass fieldClassRef, jboolean isStatic, jboolean isFinal, jfieldID fid); void JType_DisposeLocalObjectRefArg(JNIEnv* jenv, jvalue* value, void* data); @@ -42,6 +45,12 @@ void JType_DisposeReadOnlyBufferArg(JNIEnv* jenv, jvalue* value, void* data); void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data); +static int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedType, int floatMatch); + +static int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedComponentType); + JPy_JType* JType_GetTypeForObject(JNIEnv* jenv, jobject objectRef) { JPy_JType* type; @@ -275,23 +284,23 @@ PyObject* JType_ConvertJavaToPythonObject(JNIEnv* jenv, JPy_JType* type, jobject if (type->componentType == NULL) { // Scalar type, not an array, try to convert to Python equivalent - if (type == JPy_JBooleanObj) { + if (type == JPy_JBooleanObj || type == JPy_JBoolean) { jboolean value = (*jenv)->CallBooleanMethod(jenv, objectRef, JPy_Boolean_BooleanValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JBOOLEAN(value); - } else if (type == JPy_JCharacterObj) { + } else if (type == JPy_JCharacterObj || type == JPy_JChar) { jchar value = (*jenv)->CallCharMethod(jenv, objectRef, JPy_Character_CharValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JCHAR(value); - } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj) { + } else if (type == JPy_JByteObj || type == JPy_JShortObj || type == JPy_JIntegerObj || type == JPy_JShort || type == JPy_JInt) { jint value = (*jenv)->CallIntMethod(jenv, objectRef, JPy_Number_IntValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JINT(value); - } else if (type == JPy_JLongObj) { + } else if (type == JPy_JLongObj || type == JPy_JLong) { jlong value = (*jenv)->CallLongMethod(jenv, objectRef, JPy_Number_LongValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JLONG(value); - } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj) { + } else if (type == JPy_JFloatObj || type == JPy_JDoubleObj || type == JPy_JFloat || type == JPy_JDouble) { jdouble value = (*jenv)->CallDoubleMethod(jenv, objectRef, JPy_Number_DoubleValue_MID); JPy_ON_JAVA_EXCEPTION_RETURN(NULL); return JPy_FROM_JDOUBLE(value); @@ -439,7 +448,7 @@ int JType_CreateJavaPyObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, job return JType_CreateJavaObject(jenv, type, pyArg, type->classRef, JPy_PyObject_Init_MID, value, objectRef); } -int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef) +int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping) { jint itemCount; jarray arrayRef; @@ -713,7 +722,7 @@ int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyAr (*jenv)->DeleteLocalRef(jenv, arrayRef); return -1; } - if (JType_ConvertPythonToJavaObject(jenv, componentType, pyItem, &jItem) < 0) { + if (JType_ConvertPythonToJavaObject(jenv, componentType, pyItem, &jItem, allowObjectWrapping) < 0) { (*jenv)->DeleteLocalRef(jenv, arrayRef); Py_DECREF(pyItem); return -1; @@ -736,8 +745,7 @@ int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyAr return 0; } - -int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, jobject* objectRef) +int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping) { // Note: There may be a potential memory leak here. // If a new local reference is created in this function and assigned to *objectRef, the reference may escape. @@ -752,9 +760,12 @@ int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyA // If it is already a Java object wrapper JObj, then we are done *objectRef = ((JPy_JObj*) pyArg)->objectRef; return 0; + } else if (JType_Check(pyArg)) { + *objectRef = ((JPy_JType*)pyArg)->classRef; + return 0; } else if (type->componentType != NULL) { // For any other Python argument create a Java object (a new local reference) - return JType_CreateJavaArray(jenv, type->componentType, pyArg, objectRef); + return JType_CreateJavaArray(jenv, type->componentType, pyArg, objectRef, allowObjectWrapping); } else if (type == JPy_JBoolean || type == JPy_JBooleanObj) { return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); } else if (type == JPy_JChar || type == JPy_JCharacterObj) { @@ -773,20 +784,20 @@ int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyA return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); } else if (type == JPy_JPyObject) { return JType_CreateJavaPyObject(jenv, type, pyArg, objectRef); - } else if (type == JPy_JObject) { - if (PyBool_Check(pyArg)) { - return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); - } else if (JPy_IS_CLONG(pyArg)) { - return JType_CreateJavaIntegerObject(jenv, type, pyArg, objectRef); - } else if (PyFloat_Check(pyArg)) { - return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); - } else if (JPy_IS_STR(pyArg)) { - return JPy_AsJString(jenv, pyArg, objectRef); - } - } else if (type == JPy_JString) { - if (JPy_IS_STR(pyArg)) { - return JPy_AsJString(jenv, pyArg, objectRef); - } + } else if (JPy_IS_STR(pyArg) && (type == JPy_JString || type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_JString->classRef, type->classRef)))) { + return JPy_AsJString(jenv, pyArg, objectRef); + } else if (PyBool_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Boolean_JClass, type->classRef)))) { + return JType_CreateJavaBooleanObject(jenv, type, pyArg, objectRef); + } else if (JPy_IS_CLONG(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Integer_JClass, type->classRef)))) { + return JType_CreateJavaIntegerObject(jenv, type, pyArg, objectRef); + } else if (JPy_IS_CLONG(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Long_JClass, type->classRef)))) { + return JType_CreateJavaLongObject(jenv, type, pyArg, objectRef); + } else if (PyFloat_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Double_JClass, type->classRef)))) { + return JType_CreateJavaDoubleObject(jenv, type, pyArg, objectRef); + } else if (PyFloat_Check(pyArg) && (type == JPy_JObject || ((*jenv)->IsAssignableFrom(jenv, JPy_Float_JClass, type->classRef)))) { + return JType_CreateJavaFloatObject(jenv, type, pyArg, objectRef); + } else if (type == JPy_JObject && allowObjectWrapping) { + return JType_CreateJavaPyObject(jenv, JPy_JPyObject, pyArg, objectRef); } return JType_PythonToJavaConversionError(type, pyArg); } @@ -870,7 +881,7 @@ jboolean JType_AcceptMethod(JPy_JType* declaringClass, JPy_JMethod* method) } -int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, const char* methodName, jclass returnType, jarray paramTypes, jboolean isStatic, jmethodID mid) +int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, const char* methodName, jclass returnType, jarray paramTypes, jboolean isStatic, jboolean isVarArgs, jmethodID mid) { JPy_ParamDescriptor* paramDescriptors = NULL; JPy_ReturnDescriptor* returnDescriptor = NULL; @@ -878,7 +889,7 @@ int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, cons JPy_JMethod* method; paramCount = (*jenv)->GetArrayLength(jenv, paramTypes); - JPy_DIAG_PRINT(JPy_DIAG_F_TYPE, "JType_ProcessMethod: methodName=\"%s\", paramCount=%d, isStatic=%d, mid=%p\n", methodName, paramCount, isStatic, mid); + JPy_DIAG_PRINT(JPy_DIAG_F_TYPE, "JType_ProcessMethod: methodName=\"%s\", paramCount=%d, isStatic=%d, isVarArgs=%d, mid=%p\n", methodName, paramCount, isStatic, isVarArgs, mid); if (paramCount > 0) { paramDescriptors = JType_CreateParamDescriptors(jenv, paramCount, paramTypes); @@ -901,7 +912,7 @@ int JType_ProcessMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodKey, cons returnDescriptor = NULL; } - method = JMethod_New(type, methodKey, paramCount, paramDescriptors, returnDescriptor, isStatic, mid); + method = JMethod_New(type, methodKey, paramCount, paramDescriptors, returnDescriptor, isStatic, isVarArgs, mid); if (method == NULL) { PyMem_Del(paramDescriptors); PyMem_Del(returnDescriptor); @@ -971,6 +982,7 @@ int JType_ProcessClassConstructors(JNIEnv* jenv, JPy_JType* type) jint constrCount; jint i; jboolean isPublic; + jboolean isVarArg; jmethodID mid; PyObject* methodKey; @@ -985,10 +997,11 @@ int JType_ProcessClassConstructors(JNIEnv* jenv, JPy_JType* type) constructor = (*jenv)->GetObjectArrayElement(jenv, constructors, i); modifiers = (*jenv)->CallIntMethod(jenv, constructor, JPy_Constructor_GetModifiers_MID); isPublic = (modifiers & 0x0001) != 0; + isVarArg = (modifiers & 0x0080) != 0; if (isPublic) { parameterTypes = (*jenv)->CallObjectMethod(jenv, constructor, JPy_Constructor_GetParameterTypes_MID); mid = (*jenv)->FromReflectedMethod(jenv, constructor); - JType_ProcessMethod(jenv, type, methodKey, JPy_JTYPE_ATTR_NAME_JINIT, NULL, parameterTypes, 1, mid); + JType_ProcessMethod(jenv, type, methodKey, JPy_JTYPE_ATTR_NAME_JINIT, NULL, parameterTypes, 1, isVarArg, mid); (*jenv)->DeleteLocalRef(jenv, parameterTypes); } (*jenv)->DeleteLocalRef(jenv, constructor); @@ -1065,7 +1078,9 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) jint methodCount; jint i; jboolean isStatic; + jboolean isVarArg; jboolean isPublic; + jboolean isBridge; const char* methodName; jmethodID mid; PyObject* methodKey; @@ -1083,7 +1098,10 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) // see http://docs.oracle.com/javase/6/docs/api/constant-values.html#java.lang.reflect.Modifier.PUBLIC isPublic = (modifiers & 0x0001) != 0; isStatic = (modifiers & 0x0008) != 0; - if (isPublic) { + isVarArg = (modifiers & 0x0080) != 0; + isBridge = (modifiers & 0x0040) != 0; + // we exclude bridge methods; as covariant return types will result in bridge methods that cause ambiguity + if (isPublic && !isBridge) { methodNameStr = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetName_MID); returnType = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetReturnType_MID); parameterTypes = (*jenv)->CallObjectMethod(jenv, method, JPy_Method_GetParameterTypes_MID); @@ -1091,7 +1109,7 @@ int JType_ProcessClassMethods(JNIEnv* jenv, JPy_JType* type) methodName = (*jenv)->GetStringUTFChars(jenv, methodNameStr, NULL); methodKey = Py_BuildValue("s", methodName); - JType_ProcessMethod(jenv, type, methodKey, methodName, returnType, parameterTypes, isStatic, mid); + JType_ProcessMethod(jenv, type, methodKey, methodName, returnType, parameterTypes, isStatic, isVarArg, mid); (*jenv)->ReleaseStringUTFChars(jenv, methodNameStr, methodName); (*jenv)->DeleteLocalRef(jenv, parameterTypes); @@ -1227,7 +1245,7 @@ void JType_InitMethodParamDescriptorFunctions(JPy_JType* type, JPy_JMethod* meth { int index; for (index = 0; index < method->paramCount; index++) { - JType_InitParamDescriptorFunctions(method->paramDescriptors + index); + JType_InitParamDescriptorFunctions(method->paramDescriptors + index, index == method->paramCount - 1 && method->isVarArgs); } } @@ -1352,7 +1370,9 @@ JPy_ParamDescriptor* JType_CreateParamDescriptors(JNIEnv* jenv, int paramCount, paramDescriptor->isOutput = 0; paramDescriptor->isReturn = 0; paramDescriptor->MatchPyArg = NULL; + paramDescriptor->MatchVarArgPyArg = NULL; paramDescriptor->ConvertPyArg = NULL; + paramDescriptor->ConvertVarArgPyArg = NULL; } return paramDescriptors; @@ -1490,6 +1510,336 @@ int JType_MatchPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr return JType_MatchPyArgAsJObject(jenv, paramDescriptor->type, pyArg); } +int JType_MatchVarArgPyArgAsJObjectParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType == NULL) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + int matchValue = JType_MatchPyArgAsJObject(jenv, componentType, unpack); + if (matchValue == 0) { + return 0; + } + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + return minMatch; +} + +int JType_MatchVarArgPyArgAsJStringParam(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != JPy_JString) { + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + int matchValue = JType_MatchPyArgAsJStringParam(jenv, paramDescriptor, unpack); + if (matchValue == 0) { + return 0; + } + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + return minMatch; +} + +int JType_MatchVarArgPyArgAsJBooleanParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != JPy_JBoolean) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (PyBool_Check(unpack)) matchValue = 100; + else if (JPy_IS_CLONG(unpack)) matchValue = 10; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_MatchVarArgPyArgAsJIntParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JInt); +} + +int JType_MatchVarArgPyArgAsJLongParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JLong); +} + +int JType_MatchVarArgPyArgAsJShortParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JShort); +} + +int JType_MatchVarArgPyArgAsJByteParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JByte); +} + +int JType_MatchVarArgPyArgAsJCharParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgIntType(paramDescriptor, pyArg, idx, JPy_JChar); +} + +int JType_MatchVarArgPyArgIntType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedComponentType) { + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != expectedComponentType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (JPy_IS_CLONG(unpack)) matchValue = 100; + else if (PyBool_Check(unpack)) matchValue = 10; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_MatchVarArgPyArgAsJDoubleParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + return JType_MatchVarArgPyArgAsFPType(paramDescriptor, pyArg, idx, JPy_JDouble, 100); +} + +int JType_MatchVarArgPyArgAsJFloatParam(JNIEnv *jenv, JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx) +{ + // float gets a match of 90, so that double has a better chance + return JType_MatchVarArgPyArgAsFPType(paramDescriptor, pyArg, idx, JPy_JFloat, 90); +} + +/* The float and double match functions are almost identical, but for the expected componentType and the match value + * for floating point numbers should give a preference to double over float. */ +int JType_MatchVarArgPyArgAsFPType(const JPy_ParamDescriptor *paramDescriptor, PyObject *pyArg, int idx, + struct JPy_JType *expectedType, int floatMatch) { + Py_ssize_t argCount = PyTuple_Size(pyArg); + Py_ssize_t remaining = (argCount - idx); + + JPy_JType *componentType = paramDescriptor->type->componentType; + PyObject *varArgs; + int minMatch = 100; + int ii; + + if (componentType != expectedType) { + // something is horribly wrong here! + return 0; + } + + if (remaining == 0) { + return 10; + } + + varArgs = PyTuple_GetSlice(pyArg, idx, argCount); + for (ii = 0; ii < remaining; ii++) { + PyObject *unpack = PyTuple_GetItem(varArgs, ii); + + int matchValue; + if (PyFloat_Check(unpack)) matchValue = floatMatch; + else if (PyNumber_Check(unpack)) matchValue = 50; + else if (JPy_IS_CLONG(unpack)) matchValue = 10; + else if (PyBool_Check(unpack)) matchValue = 1; + else return 0; + minMatch = matchValue < minMatch ? matchValue : minMatch; + } + + return minMatch; +} + +int JType_ConvertVarArgPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescriptor, PyObject* pyArgOrig, int offset, jvalue* value, JPy_ArgDisposer* disposer) +{ + Py_ssize_t size = PyTuple_Size(pyArgOrig); + PyObject *pyArg = PyTuple_GetSlice(pyArgOrig, offset, size); + + if (pyArg == Py_None) { + // Py_None maps to (Java) NULL + value->l = NULL; + disposer->data = NULL; + disposer->DisposeArg = NULL; + } else if (JObj_Check(pyArg)) { + // If it is a wrapped Java object, it is always a global reference, so don't dispose it + JPy_JObj* obj = (JPy_JObj*) pyArg; + value->l = obj->objectRef; + disposer->data = NULL; + disposer->DisposeArg = NULL; + } else { + // For any other Python argument, we first check if the formal parameter is a primitive array + // and the Python argument is a buffer object + + JPy_JType* paramType = paramDescriptor->type; + JPy_JType* paramComponentType = paramType->componentType; + + if (paramComponentType != NULL && paramComponentType->isPrimitive && PyObject_CheckBuffer(pyArg)) { + Py_buffer* pyBuffer; + int flags; + Py_ssize_t itemCount; + jarray jArray; + void* arrayItems; + jint itemSize; + + pyBuffer = PyMem_New(Py_buffer, 1); + if (pyBuffer == NULL) { + PyErr_NoMemory(); + return -1; + } + + flags = paramDescriptor->isMutable ? PyBUF_WRITABLE : PyBUF_SIMPLE; + if (PyObject_GetBuffer(pyArg, pyBuffer, flags) < 0) { + PyMem_Del(pyBuffer); + return -1; + } + + itemCount = pyBuffer->len / pyBuffer->itemsize; + if (itemCount <= 0) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_Format(PyExc_ValueError, "illegal buffer argument: not a positive item count: %ld", itemCount); + return -1; + } + + if (paramComponentType == JPy_JBoolean) { + jArray = (*jenv)->NewBooleanArray(jenv, itemCount); + itemSize = sizeof(jboolean); + } else if (paramComponentType == JPy_JByte) { + jArray = (*jenv)->NewByteArray(jenv, itemCount); + itemSize = sizeof(jbyte); + } else if (paramComponentType == JPy_JChar) { + jArray = (*jenv)->NewCharArray(jenv, itemCount); + itemSize = sizeof(jchar); + } else if (paramComponentType == JPy_JShort) { + jArray = (*jenv)->NewShortArray(jenv, itemCount); + itemSize = sizeof(jshort); + } else if (paramComponentType == JPy_JInt) { + jArray = (*jenv)->NewIntArray(jenv, itemCount); + itemSize = sizeof(jint); + } else if (paramComponentType == JPy_JLong) { + jArray = (*jenv)->NewLongArray(jenv, itemCount); + itemSize = sizeof(jlong); + } else if (paramComponentType == JPy_JFloat) { + jArray = (*jenv)->NewFloatArray(jenv, itemCount); + itemSize = sizeof(jfloat); + } else if (paramComponentType == JPy_JDouble) { + jArray = (*jenv)->NewDoubleArray(jenv, itemCount); + itemSize = sizeof(jdouble); + } else { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_SetString(PyExc_RuntimeError, "internal error: illegal primitive Java type"); + return -1; + } + + if (pyBuffer->len != itemCount * itemSize) { + Py_ssize_t bufferLen = pyBuffer->len; + Py_ssize_t bufferItemSize = pyBuffer->itemsize; + //printf("%ld, %ld, %ld, %ld\n", pyBuffer->len , pyBuffer->itemsize, itemCount, itemSize); + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_Format(PyExc_ValueError, + "illegal buffer argument: expected size was %ld bytes, but got %ld (expected item size was %d bytes, got %ld)", + itemCount * itemSize, bufferLen, itemSize, bufferItemSize); + return -1; + } + + if (jArray == NULL) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_NoMemory(); + return -1; + } + + if (!paramDescriptor->isOutput) { + arrayItems = (*jenv)->GetPrimitiveArrayCritical(jenv, jArray, NULL); + if (arrayItems == NULL) { + PyBuffer_Release(pyBuffer); + PyMem_Del(pyBuffer); + PyErr_NoMemory(); + return -1; + } + JPy_DIAG_PRINT(JPy_DIAG_F_EXEC|JPy_DIAG_F_MEM, "JType_ConvertPyArgToJObjectArg: moving Python buffer into Java array: pyBuffer->buf=%p, pyBuffer->len=%d\n", pyBuffer->buf, pyBuffer->len); + memcpy(arrayItems, pyBuffer->buf, itemCount * itemSize); + (*jenv)->ReleasePrimitiveArrayCritical(jenv, jArray, arrayItems, 0); + } + + value->l = jArray; + disposer->data = pyBuffer; + disposer->DisposeArg = paramDescriptor->isMutable ? JType_DisposeWritableBufferArg : JType_DisposeReadOnlyBufferArg; + } else { + jobject objectRef; + if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef, JNI_FALSE) < 0) { + return -1; + } + value->l = objectRef; + disposer->data = NULL; + disposer->DisposeArg = JType_DisposeLocalObjectRefArg; + } + } + + return 0; +} + int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyArg) { JPy_JType* argType; @@ -1618,6 +1968,23 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr return matchValue; } } else if (PySequence_Check(pyArg)) { + // if we know the type of the array is a string, we should preferentially match it + if ((*jenv)->IsAssignableFrom(jenv, paramComponentType->classRef, JPy_String_JClass)) { + // it's a string array + Py_ssize_t len = PySequence_Length(pyArg); + Py_ssize_t ii; + + for (ii = 0; ii < len; ++ii) { + PyObject *element = PySequence_GetItem(pyArg, ii); + if (!JPy_IS_STR(element)) { + // if the element is not a string, this is not a good match + return 0; + } + } + + // a String sequence is a good match for a String array + return 80; + } return 10; } } else if (paramType == JPy_JObject) { @@ -1648,6 +2015,33 @@ int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* paramType, PyObject* pyAr } else if (PyBool_Check(pyArg)) { return 10; } + } else { + if (JPy_IS_STR(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_JString->classRef, paramType->classRef)) { + return 80; + } + } + else if (PyBool_Check(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Boolean_JClass, paramType->classRef)) { + return 80; + } + } + else if (JPy_IS_CLONG(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Integer_JClass, paramType->classRef)) { + return 80; + } + else if ((*jenv)->IsAssignableFrom(jenv, JPy_Long_JClass, paramType->classRef)) { + return 80; + } + } + else if (PyFloat_Check(pyArg)) { + if ((*jenv)->IsAssignableFrom(jenv, JPy_Double_JClass, paramType->classRef)) { + return 80; + } + else if ((*jenv)->IsAssignableFrom(jenv, JPy_Float_JClass, paramType->classRef)) { + return 80; + } + } } return 0; @@ -1697,7 +2091,7 @@ int JType_ConvertPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr if (itemCount <= 0) { PyBuffer_Release(pyBuffer); PyMem_Del(pyBuffer); - PyErr_Format(PyExc_ValueError, "illegal buffer argument: negative item count: %ld", itemCount); + PyErr_Format(PyExc_ValueError, "illegal buffer argument: not a positive item count: %ld", itemCount); return -1; } @@ -1769,7 +2163,7 @@ int JType_ConvertPyArgToJObjectArg(JNIEnv* jenv, JPy_ParamDescriptor* paramDescr disposer->DisposeArg = paramDescriptor->isMutable ? JType_DisposeWritableBufferArg : JType_DisposeReadOnlyBufferArg; } else { jobject objectRef; - if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef) < 0) { + if (JType_ConvertPythonToJavaObject(jenv, paramType, pyArg, &objectRef, JNI_FALSE) < 0) { return -1; } value->l = objectRef; @@ -1839,7 +2233,7 @@ void JType_DisposeWritableBufferArg(JNIEnv* jenv, jvalue* value, void* data) } } -void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) +void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor, jboolean isLastVarArg) { JPy_JType* paramType = paramDescriptor->type; @@ -1880,6 +2274,31 @@ void JType_InitParamDescriptorFunctions(JPy_ParamDescriptor* paramDescriptor) paramDescriptor->MatchPyArg = JType_MatchPyArgAsJObjectParam; paramDescriptor->ConvertPyArg = JType_ConvertPyArgToJObjectArg; } + if (isLastVarArg) { + paramDescriptor->ConvertVarArgPyArg = JType_ConvertVarArgPyArgToJObjectArg; + + if (paramType->componentType == JPy_JBoolean) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJBooleanParam; + } else if (paramType->componentType == JPy_JByte) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJByteParam; + } else if (paramType->componentType == JPy_JChar) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJCharParam; + } else if (paramType->componentType == JPy_JShort) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJShortParam; + } else if (paramType->componentType == JPy_JInt) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJIntParam; + } else if (paramType->componentType == JPy_JLong) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJLongParam; + } else if (paramType->componentType == JPy_JFloat) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJFloatParam; + } else if (paramType->componentType == JPy_JDouble) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJDoubleParam; + } else if (paramType->componentType == JPy_JString) { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJStringParam; + } else { + paramDescriptor->MatchVarArgPyArg = JType_MatchVarArgPyArgAsJObjectParam; + } + } } /** diff --git a/src/main/c/jpy_jtype.h b/src/main/c/jpy_jtype.h index d8ffb4b90c..83b94df54a 100644 --- a/src/main/c/jpy_jtype.h +++ b/src/main/c/jpy_jtype.h @@ -12,6 +12,10 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * + * This file was modified by Illumon. + * */ #ifndef JPY_JTYPE_H @@ -74,7 +78,9 @@ JPy_ArgDisposer; struct JPy_ParamDescriptor; typedef int (*JPy_MatchPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*); +typedef int (*JPy_MatchVarArgPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, int); typedef int (*JPy_ConvertPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, jvalue*, JPy_ArgDisposer*); +typedef int (*JPy_ConvertVarArgPyArg)(JNIEnv*, struct JPy_ParamDescriptor*, PyObject*, int, jvalue*, JPy_ArgDisposer*); /** * Method return value descriptor. @@ -103,7 +109,9 @@ typedef struct JPy_ParamDescriptor jboolean isOutput; jboolean isReturn; JPy_MatchPyArg MatchPyArg; + JPy_MatchVarArgPyArg MatchVarArgPyArg; JPy_ConvertPyArg ConvertPyArg; + JPy_ConvertVarArgPyArg ConvertVarArgPyArg; } JPy_ParamDescriptor; @@ -115,13 +123,13 @@ JPy_JType* JType_GetTypeForName(JNIEnv* jenv, const char* typeName, jboolean res JPy_JType* JType_GetType(JNIEnv* jenv, jclass classRef, jboolean resolve); PyObject* JType_ConvertJavaToPythonObject(JNIEnv* jenv, JPy_JType* type, jobject objectRef); -int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* arg, jobject* objectRef); +int JType_ConvertPythonToJavaObject(JNIEnv* jenv, JPy_JType* type, PyObject* arg, jobject* objectRef, jboolean allowObjectWrapping); PyObject* JType_GetOverloadedMethod(JNIEnv* jenv, JPy_JType* type, PyObject* methodName, jboolean useSuperClass); int JType_MatchPyArgAsJObject(JNIEnv* jenv, JPy_JType* type, PyObject* pyArg); -int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef); +int JType_CreateJavaArray(JNIEnv* jenv, JPy_JType* componentType, PyObject* pyArg, jobject* objectRef, jboolean allowObjectWrapping); // Non-API. Defined in jpy_jobj.c int JType_InitSlots(JPy_JType* type); diff --git a/src/main/c/jpy_module.c b/src/main/c/jpy_module.c index a5c25df195..fe19389951 100644 --- a/src/main/c/jpy_module.c +++ b/src/main/c/jpy_module.c @@ -12,10 +12,14 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #include "jpy_module.h" #include "jpy_diag.h" +#include "jpy_verboseexcept.h" #include "jpy_jtype.h" #include "jpy_jmethod.h" #include "jpy_jfield.h" @@ -85,6 +89,7 @@ static struct PyModuleDef JPy_ModuleDef = PyObject* JPy_Module = NULL; PyObject* JPy_Types = NULL; PyObject* JPy_Type_Callbacks = NULL; +PyObject* JPy_Type_Translations = NULL; PyObject* JException_Type = NULL; // A global reference to a Java VM singleton. @@ -119,6 +124,8 @@ JPy_JType* JPy_JClass = NULL; JPy_JType* JPy_JString = NULL; JPy_JType* JPy_JPyObject = NULL; JPy_JType* JPy_JPyModule = NULL; +JPy_JType* JPy_JThrowable = NULL; +JPy_JType* JPy_JStackTraceElement = NULL; // java.lang.Comparable @@ -161,7 +168,28 @@ jmethodID JPy_Field_GetName_MID = NULL; jmethodID JPy_Field_GetModifiers_MID = NULL; jmethodID JPy_Field_GetType_MID = NULL; +// java.util.Map +jclass JPy_Map_JClass = NULL; +jclass JPy_Map_Entry_JClass = NULL; +jmethodID JPy_Map_entrySet_MID = NULL; +jmethodID JPy_Map_put_MID = NULL; +jmethodID JPy_Map_clear_MID = NULL; +jmethodID JPy_Map_Entry_getKey_MID = NULL; +jmethodID JPy_Map_Entry_getValue_MID = NULL; +// java.util.Set +jclass JPy_Set_JClass = NULL; +jmethodID JPy_Set_Iterator_MID = NULL; +// java.util.Iterator +jclass JPy_Iterator_JClass = NULL; +jmethodID JPy_Iterator_next_MID = NULL; +jmethodID JPy_Iterator_hasNext_MID = NULL; + jclass JPy_RuntimeException_JClass = NULL; +jclass JPy_OutOfMemoryError_JClass = NULL; +jclass JPy_UnsupportedOperationException_JClass = NULL; +jclass JPy_FileNotFoundException_JClass = NULL; +jclass JPy_KeyError_JClass = NULL; +jclass JPy_StopIteration_JClass = NULL; // java.lang.Boolean jclass JPy_Boolean_JClass = NULL; @@ -198,11 +226,23 @@ jmethodID JPy_Number_DoubleValue_MID = NULL; jclass JPy_Void_JClass = NULL; jclass JPy_String_JClass = NULL; +jclass JPy_PyObject_JClass = NULL; +jclass JPy_PyDictWrapper_JClass = NULL; jmethodID JPy_PyObject_GetPointer_MID = NULL; jmethodID JPy_PyObject_Init_MID = NULL; jmethodID JPy_PyModule_Init_MID = NULL; +jmethodID JPy_PyDictWrapper_GetPointer_MID = NULL; + +// java.lang.Throwable +jclass JPy_Throwable_JClass = NULL; +jmethodID JPy_Throwable_getStackTrace_MID = NULL; +jmethodID JPy_Throwable_getCause_MID = NULL; + +// stack trace element +jclass JPy_StackTraceElement_JClass = NULL; + // }}} @@ -324,6 +364,12 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void) ///////////////////////////////////////////////////////////////////////// + JPy_Type_Translations = PyDict_New(); + Py_INCREF(JPy_Type_Translations); + PyModule_AddObject(JPy_Module, JPy_MODULE_ATTR_NAME_TYPE_TRANSLATIONS, JPy_Type_Translations); + + ///////////////////////////////////////////////////////////////////////// + if (PyType_Ready(&Diag_Type) < 0) { JPY_RETURN(NULL); } @@ -334,6 +380,15 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void) PyModule_AddObject(JPy_Module, "diag", pyDiag); } + if (PyType_Ready(&VerboseExceptions_Type) < 0) { + JPY_RETURN(NULL); + } + { + PyObject* pyVerboseExceptions = VerboseExceptions_New(); + Py_INCREF(pyVerboseExceptions); + PyModule_AddObject(JPy_Module, "VerboseExceptions", pyVerboseExceptions); + } + ///////////////////////////////////////////////////////////////////////// if (JPy_JVM != NULL) { @@ -582,12 +637,12 @@ PyObject* JPy_array(PyObject* self, PyObject* args) if (arrayRef == NULL) { return PyErr_NoMemory(); } - return (PyObject*) JObj_New(jenv, arrayRef); + return JObj_New(jenv, arrayRef); } else if (PySequence_Check(objInit)) { - if (JType_CreateJavaArray(jenv, componentType, objInit, &arrayRef) < 0) { + if (JType_CreateJavaArray(jenv, componentType, objInit, &arrayRef, JNI_FALSE) < 0) { return NULL; } - return (PyObject*) JObj_New(jenv, arrayRef); + return JObj_New(jenv, arrayRef); } else { PyErr_SetString(PyExc_ValueError, "array: argument 2 (init) must be either an integer array length or any sequence"); return NULL; @@ -692,14 +747,19 @@ jmethodID JPy_GetMethod(JNIEnv* jenv, jclass classRef, const char* name, const c int initGlobalPyObjectVars(JNIEnv* jenv) { + JPy_JType *dictType; + JPy_JType *keyErrorType; + JPy_JType *stopIterationType; + JPy_JPyObject = JType_GetTypeForName(jenv, "org.jpy.PyObject", JNI_FALSE); if (JPy_JPyObject == NULL) { // org.jpy.PyObject may not be on the classpath, which is ok PyErr_Clear(); return -1; } else { - DEFINE_METHOD(JPy_PyObject_GetPointer_MID, JPy_JPyObject->classRef, "getPointer", "()J"); - DEFINE_METHOD(JPy_PyObject_Init_MID, JPy_JPyObject->classRef, "", "(J)V"); + JPy_PyObject_JClass = JPy_JPyObject->classRef; + DEFINE_METHOD(JPy_PyObject_GetPointer_MID, JPy_PyObject_JClass, "getPointer", "()J"); + DEFINE_METHOD(JPy_PyObject_Init_MID, JPy_PyObject_JClass, "", "(J)V"); } JPy_JPyModule = JType_GetTypeForName(jenv, "org.jpy.PyModule", JNI_FALSE); @@ -708,6 +768,32 @@ int initGlobalPyObjectVars(JNIEnv* jenv) PyErr_Clear(); return -1; } + + dictType = JType_GetTypeForName(jenv, "org.jpy.PyDictWrapper", JNI_FALSE); + if (dictType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_PyDictWrapper_JClass = dictType->classRef; + DEFINE_METHOD(JPy_PyDictWrapper_GetPointer_MID, JPy_PyDictWrapper_JClass, "getPointer", "()J"); + } + + keyErrorType = JType_GetTypeForName(jenv, "org.jpy.KeyError", JNI_FALSE); + if (keyErrorType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_KeyError_JClass = keyErrorType->classRef; + } + + stopIterationType = JType_GetTypeForName(jenv, "org.jpy.StopIteration", JNI_FALSE); + if (stopIterationType == NULL) { + PyErr_Clear(); + return -1; + } else { + JPy_StopIteration_JClass = stopIterationType->classRef; + } + return 0; } @@ -752,7 +838,28 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_METHOD(JPy_Method_GetParameterTypes_MID, JPy_Method_JClass, "getParameterTypes", "()[Ljava/lang/Class;"); DEFINE_METHOD(JPy_Method_GetReturnType_MID, JPy_Method_JClass, "getReturnType", "()Ljava/lang/Class;"); + DEFINE_CLASS(JPy_Map_JClass, "java/util/Map"); + DEFINE_METHOD(JPy_Map_entrySet_MID, JPy_Map_JClass, "entrySet", "()Ljava/util/Set;"); + DEFINE_METHOD(JPy_Map_put_MID, JPy_Map_JClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Map_clear_MID, JPy_Map_JClass, "clear", "()V"); + + DEFINE_CLASS(JPy_Map_Entry_JClass, "java/util/Map$Entry"); + DEFINE_METHOD(JPy_Map_Entry_getKey_MID, JPy_Map_Entry_JClass, "getKey", "()Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Map_Entry_getValue_MID, JPy_Map_Entry_JClass, "getValue", "()Ljava/lang/Object;"); + + + // java.util.Set + DEFINE_CLASS(JPy_Set_JClass, "java/util/Set"); + DEFINE_METHOD(JPy_Set_Iterator_MID, JPy_Set_JClass, "iterator", "()Ljava/util/Iterator;"); + // java.util.Iterator + DEFINE_CLASS(JPy_Iterator_JClass, "java/util/Iterator"); + DEFINE_METHOD(JPy_Iterator_next_MID, JPy_Iterator_JClass, "next", "()Ljava/lang/Object;"); + DEFINE_METHOD(JPy_Iterator_hasNext_MID, JPy_Iterator_JClass, "hasNext", "()Z"); + DEFINE_CLASS(JPy_RuntimeException_JClass, "java/lang/RuntimeException"); + DEFINE_CLASS(JPy_OutOfMemoryError_JClass, "java/lang/OutOfMemoryError"); + DEFINE_CLASS(JPy_FileNotFoundException_JClass, "java/io/FileNotFoundException"); + DEFINE_CLASS(JPy_UnsupportedOperationException_JClass, "java/lang/UnsupportedOperationException"); DEFINE_CLASS(JPy_Boolean_JClass, "java/lang/Boolean"); DEFINE_METHOD(JPy_Boolean_Init_MID, JPy_Boolean_JClass, "", "(Z)V"); @@ -788,6 +895,8 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_CLASS(JPy_Void_JClass, "java/lang/Void"); DEFINE_CLASS(JPy_String_JClass, "java/lang/String"); + DEFINE_CLASS(JPy_Throwable_JClass, "java/lang/Throwable"); + DEFINE_CLASS(JPy_StackTraceElement_JClass, "java/lang/StackTraceElement"); // Non-Object types: Primitive types and void. DEFINE_NON_OBJECT_TYPE(JPy_JBoolean, JPy_Boolean_JClass); @@ -814,6 +923,10 @@ int JPy_InitGlobalVars(JNIEnv* jenv) DEFINE_OBJECT_TYPE(JPy_JDoubleObj, JPy_Double_JClass); // Other objects. DEFINE_OBJECT_TYPE(JPy_JString, JPy_String_JClass); + DEFINE_OBJECT_TYPE(JPy_JThrowable, JPy_Throwable_JClass); + DEFINE_OBJECT_TYPE(JPy_JStackTraceElement, JPy_StackTraceElement_JClass); + DEFINE_METHOD(JPy_Throwable_getCause_MID, JPy_Throwable_JClass, "getCause", "()Ljava/lang/Throwable;"); + DEFINE_METHOD(JPy_Throwable_getStackTrace_MID, JPy_Throwable_JClass, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); // JType_AddClassAttribute is actually called from within JType_GetType(), but not for // JPy_JObject and JPy_JClass for an obvious reason. So we do it now: @@ -947,31 +1060,189 @@ void JPy_ClearGlobalVars(JNIEnv* jenv) JPy_JPyModule = NULL; } +#define AT_STRING "\tat " +#define AT_STRLEN 4 +#define CAUSED_BY_STRING "caused by " +#define CAUSED_BY_STRLEN 10 +#define ELIDED_STRING_MAX_SIZE 30 void JPy_HandleJavaException(JNIEnv* jenv) { jthrowable error = (*jenv)->ExceptionOccurred(jenv); if (error != NULL) { jstring message; + int allocError = 0; if (JPy_DiagFlags != 0) { (*jenv)->ExceptionDescribe(jenv); } - message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID); - if (message != NULL) { - const char* messageChars; - - messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); - if (messageChars != NULL) { - PyErr_Format(PyExc_RuntimeError, "%s", messageChars); - (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + if (JPy_VerboseExceptions) { + char *stackTraceString; + size_t stackTraceLength = 0; + jthrowable cause = error; + jarray enclosingElements = NULL; + jint enclosingSize = 0; + + stackTraceString = strdup(""); + + do { + /* We want the type and the detail string, which is actually what a Throwable toString() does by + * default, as does the default printStackTrace(). */ + jint ii; + + jarray stackTrace; + jint stackTraceElements; + jint lastElementToPrint; + jint enclosingIndex; + + if (stackTraceLength > 0) { + char *newStackString; + + newStackString = realloc(stackTraceString, CAUSED_BY_STRLEN + 1 + stackTraceLength); + if (newStackString == NULL) { + allocError = 1; + break; + } + stackTraceString = newStackString; + strcat(stackTraceString, CAUSED_BY_STRING); + stackTraceLength += CAUSED_BY_STRLEN; + } + + message = (jstring) (*jenv)->CallObjectMethod(jenv, cause, JPy_Object_ToString_MID); + if (message != NULL) { + const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars != NULL) { + char *newStackString; + size_t len = strlen(messageChars); + + newStackString = realloc(stackTraceString, len + 2 + stackTraceLength); + if (newStackString == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + allocError = 1; + break; + } + + stackTraceString = newStackString; + strcat(stackTraceString, messageChars); + stackTraceString[stackTraceLength + len] = '\n'; + stackTraceString[stackTraceLength + len + 1] = '\0'; + stackTraceLength += (len + 1); + + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } else { + allocError = 1; + break; + } + (*jenv)->DeleteLocalRef(jenv, message); + } + + /* We should assemble a string based on the stack trace. */ + stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID); + stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace); + lastElementToPrint = stackTraceElements - 1; + enclosingIndex = enclosingSize - 1; + + while (lastElementToPrint >= 0 && enclosingIndex >= 0) { + jobject thisElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, lastElementToPrint); + jobject thatElement = (*jenv)->GetObjectArrayElement(jenv, enclosingElements, enclosingIndex); + + // if they are equal, let's decrement, otherwise we break + jboolean equal = (*jenv)->CallBooleanMethod(jenv, thisElement, JPy_Object_Equals_MID, thatElement); + if (!equal) { + break; + } + + lastElementToPrint--; + enclosingIndex--; + } + + for (ii = 0; ii <= lastElementToPrint; ++ii) { + jobject traceElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, ii); + if (traceElement != NULL) { + message = (jstring) (*jenv)->CallObjectMethod(jenv, traceElement, JPy_Object_ToString_MID); + if (message != NULL) { + size_t len; + char *newStackString; + const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars == NULL) { + allocError = 1; + break; + } + + len = strlen(messageChars); + + newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength); + if (newStackString == NULL) { + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + allocError = 1; + break; + } + + stackTraceString = newStackString; + strcat(stackTraceString, AT_STRING); + strcat(stackTraceString, messageChars); + stackTraceString[stackTraceLength + len + AT_STRLEN] = '\n'; + stackTraceString[stackTraceLength + len + AT_STRLEN + 1] = '\0'; + stackTraceLength += (len + 1 + AT_STRLEN); + + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } + + } + } + + if (lastElementToPrint < stackTraceElements - 1) { + int written; + char *newStackString = realloc(stackTraceString, stackTraceLength + ELIDED_STRING_MAX_SIZE); + if (newStackString == NULL) { + allocError = 1; + break; + } + + stackTraceString = newStackString; + stackTraceString[stackTraceLength + ELIDED_STRING_MAX_SIZE - 1] = '\0'; + + written = snprintf(stackTraceString + stackTraceLength, ELIDED_STRING_MAX_SIZE - 1, "\t... %d more\n", (stackTraceElements - lastElementToPrint) - 1); + if (written > (ELIDED_STRING_MAX_SIZE - 1)) { + stackTraceLength += (ELIDED_STRING_MAX_SIZE - 1); + } else { + stackTraceLength += written; + } + } + + /** So we can eliminate extra entries. */ + enclosingElements = stackTrace; + enclosingSize = stackTraceElements; + + /** Now the next cause. */ + cause = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getCause_MID); + } while (cause != NULL && !allocError); + + if (allocError == 0 && stackTraceString != NULL) { + PyErr_Format(PyExc_RuntimeError, "%s", stackTraceString); } else { - PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, but failed to allocate message text"); + PyErr_SetString(PyExc_RuntimeError, + "Java VM exception occurred, but failed to allocate message text"); } - (*jenv)->DeleteLocalRef(jenv, message); + free(stackTraceString); } else { - PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message"); + message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID); + if (message != NULL) { + const char *messageChars; + + messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL); + if (messageChars != NULL) { + PyErr_Format(PyExc_RuntimeError, "%s", messageChars); + (*jenv)->ReleaseStringUTFChars(jenv, message, messageChars); + } else { + PyErr_SetString(PyExc_RuntimeError, + "Java VM exception occurred, but failed to allocate message text"); + } + (*jenv)->DeleteLocalRef(jenv, message); + } else { + PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message"); + } } (*jenv)->DeleteLocalRef(jenv, error); @@ -987,6 +1258,7 @@ void JPy_free(void* unused) JPy_Module = NULL; JPy_Types = NULL; JPy_Type_Callbacks = NULL; + JPy_Type_Translations = NULL; JException_Type = NULL; JPy_DIAG_PRINT(JPy_DIAG_F_ALL, "JPy_free: done freeing module data\n"); diff --git a/src/main/c/jpy_module.h b/src/main/c/jpy_module.h index 6ded2c8093..7070da2b6c 100644 --- a/src/main/c/jpy_module.h +++ b/src/main/c/jpy_module.h @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ #ifndef JPY_MODULE_H @@ -32,6 +35,7 @@ extern "C" { extern PyObject* JPy_Module; extern PyObject* JPy_Types; extern PyObject* JPy_Type_Callbacks; +extern PyObject* JPy_Type_Translations; extern PyObject* JException_Type; extern JavaVM* JPy_JVM; @@ -42,6 +46,7 @@ extern jboolean JPy_MustDestroyJVM; #define JPy_MODULE_ATTR_NAME_TYPES "types" #define JPy_MODULE_ATTR_NAME_TYPE_CALLBACKS "type_callbacks" +#define JPy_MODULE_ATTR_NAME_TYPE_TRANSLATIONS "type_translations" /** @@ -148,8 +153,28 @@ extern jclass JPy_Field_JClass; extern jmethodID JPy_Field_GetName_MID; extern jmethodID JPy_Field_GetModifiers_MID; extern jmethodID JPy_Field_GetType_MID; +// java.util.Map +extern jclass JPy_Map_JClass; +extern jclass JPy_Map_Entry_JClass; +extern jmethodID JPy_Map_entrySet_MID; +extern jmethodID JPy_Map_put_MID; +extern jmethodID JPy_Map_clear_MID; +extern jmethodID JPy_Map_Entry_getKey_MID; +extern jmethodID JPy_Map_Entry_getValue_MID; +// java.util.Set +extern jclass JPy_Set_JClass; +extern jmethodID JPy_Set_Iterator_MID; +// java.util.Iterator +extern jclass JPy_Iterator_JClass; +extern jmethodID JPy_Iterator_next_MID; +extern jmethodID JPy_Iterator_hasNext_MID; extern jclass JPy_RuntimeException_JClass; +extern jclass JPy_OutOfMemoryError_JClass; +extern jclass JPy_FileNotFoundException_JClass; +extern jclass JPy_UnsupportedOperationException_JClass; +extern jclass JPy_KeyError_JClass; +extern jclass JPy_StopIteration_JClass; extern jclass JPy_Boolean_JClass; extern jmethodID JPy_Boolean_Init_MID; @@ -185,9 +210,13 @@ extern jmethodID JPy_Number_DoubleValue_MID; extern jclass JPy_String_JClass; extern jclass JPy_Void_JClass; +extern jclass JPy_PyObject_JClass; extern jmethodID JPy_PyObject_GetPointer_MID; extern jmethodID JPy_PyObject_Init_MID; +extern jclass JPy_PyDictWrapper_JClass; +extern jmethodID JPy_PyDictWrapper_GetPointer_MID; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/main/c/jpy_verboseexcept.c b/src/main/c/jpy_verboseexcept.c new file mode 100644 index 0000000000..3c96fcf327 --- /dev/null +++ b/src/main/c/jpy_verboseexcept.c @@ -0,0 +1,97 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ + +#include +#include "jpy_verboseexcept.h" + +int JPy_VerboseExceptions = 0; + +PyObject* VerboseExceptions_New(void) +{ + return PyObject_New(PyObject, &VerboseExceptions_Type); +} + + +PyObject* VerboseExceptions_getattro(PyObject* self, PyObject *attr_name) +{ + if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) { + return PyBool_FromLong(JPy_VerboseExceptions); + } else { + return PyObject_GenericGetAttr(self, attr_name); + } +} + + +int VerboseExceptions_setattro(PyObject* self, PyObject *attr_name, PyObject *v) +{ + if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) { + if (PyBool_Check(v)) { + JPy_VerboseExceptions = v == Py_True; + } else { + PyErr_SetString(PyExc_ValueError, "value for 'flags' must be a boolean"); + return -1; + } + return 0; + } else { + return PyObject_GenericSetAttr(self, attr_name, v); + } +} + + +PyTypeObject VerboseExceptions_Type = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "jpy.VerboseExceptions", /* tp_name */ + sizeof (VerboseExceptions_Type), /* tp_basicsize */ + 0, /* tp_itemsize */ + NULL, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_reserved */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + NULL, /* tp_str */ + (getattrofunc) VerboseExceptions_getattro, /* tp_getattro */ + (setattrofunc) VerboseExceptions_setattro, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Controls python exception verbosity", /* tp_doc */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + NULL, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc) NULL, /* tp_init */ + NULL, /* tp_alloc */ + NULL, /* tp_new */ +}; diff --git a/src/main/c/jpy_verboseexcept.h b/src/main/c/jpy_verboseexcept.h new file mode 100644 index 0000000000..990972127b --- /dev/null +++ b/src/main/c/jpy_verboseexcept.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ + +#ifndef JPY_VERBOSEEXCEPT_H +#define JPY_VERBOSEEXCEPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "jpy_compat.h" + +extern PyTypeObject VerboseExceptions_Type; +extern int JPy_VerboseExceptions; + +PyObject* VerboseExceptions_New(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !JPY_DIAG_H */ \ No newline at end of file diff --git a/src/main/java/org/jpy/KeyError.java b/src/main/java/org/jpy/KeyError.java new file mode 100644 index 0000000000..c3a44c5604 --- /dev/null +++ b/src/main/java/org/jpy/KeyError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +/** + * Translation of Python KeyErrors so that they can be programmatically detected from Java. + */ +public class KeyError extends RuntimeException { + KeyError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/org/jpy/PyDictWrapper.java b/src/main/java/org/jpy/PyDictWrapper.java new file mode 100644 index 0000000000..7bf97d0406 --- /dev/null +++ b/src/main/java/org/jpy/PyDictWrapper.java @@ -0,0 +1,258 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +import java.util.*; + +/** + * A simple wrapper around PyObjects that are actually Python dictionaries, to present the most useful parts of a + * Map interface. + */ +public class PyDictWrapper implements Map { + private PyObject pyObject; + + PyDictWrapper(PyObject pyObject) { + this.pyObject = pyObject; + } + + @Override + public int size() { + return pyObject.callMethod("__len__").getIntValue(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return pyObject.callMethod("has_key", key).getBooleanValue(); + } + + /** + * An extension to the Map interface that allows the use of String keys without generating warnings. + */ + public boolean containsKey(String key) { + return containsKey((Object)key); + } + + @Override + public boolean containsValue(Object value) { + return pyObject.callMethod("values").asList().contains(value); + } + + @Override + public PyObject get(Object key) { + return pyObject.callMethod("__getitem__", key); + } + + /** + * An extension to the Map interface that allows the use of String keys without generating warnings. + */ + public PyObject get(String key) { + return pyObject.callMethod("__getitem__", key); + } + + @Override + public PyObject put(PyObject key, PyObject value) { + return putObject(key, value); + } + + /** + * An extension to the Map interface that allows the use of Object key-values without generating warnings. + */ + public PyObject putObject(Object key, Object value) { + return pyObject.callMethod("__setitem__", key, value); + } + + @Override + public PyObject remove(Object key) { + try { + PyObject value = get(key); + pyObject.callMethod("__delitem__", key); + return value; + } catch (KeyError ke) { + return null; + } + } + + public PyObject remove(String key) { + return remove((Object)key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + pyObject.callMethod("clear"); + } + + @Override + public Set keySet() { + return new LinkedHashSet<>(pyObject.callMethod("keys").asList()); + } + + @Override + public Collection values() { + return pyObject.callMethod("values").asList(); + } + + @Override + public Set> entrySet() { + return new EntrySet(); + } + + /** + * Gets the underlying PyObject. + * + * @return the PyObject wrapped by this dictionary. + */ + public PyObject unwrap() { + return pyObject; + } + + /** + * Gets the underlying pointer for this object. + * + * @return the pointer to the underlying Python object wrapped by this dictionary. + */ + long getPointer() { + return pyObject.getPointer(); + } + + /** + * Copy this dictionary into a new dictionary. + * + * @return a wrapped copy of this Python dictionary. + */ + public PyDictWrapper copy() { + return new PyDictWrapper(PyLib.copyDict(pyObject.getPointer())); + } + + private class EntrySet implements Set> { + @Override + public int size() { + return PyDictWrapper.this.size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator> iterator() { + return new Iterator>() { + PyModule builtins = PyModule.getBuiltins(); + PyObject it = pyObject.callMethod("__iter__"); + PyObject next = prepareNext(); + + private PyObject prepareNext() { + try { + return next = builtins.call("next", it); + } catch (StopIteration e) { + return next = null; + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Entry next() { + final PyObject oldNext = next; + prepareNext(); + return new Entry() { + + @Override + public PyObject getKey() { + return oldNext; + } + + @Override + public PyObject getValue() { + return get(oldNext); + } + + @Override + public PyObject setValue(PyObject value) { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(Entry pyObjectPyObjectEntry) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/org/jpy/PyLib.java b/src/main/java/org/jpy/PyLib.java index a34c00f6b1..7c1509c42a 100644 --- a/src/main/java/org/jpy/PyLib.java +++ b/src/main/java/org/jpy/PyLib.java @@ -12,11 +12,15 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ package org.jpy; import java.io.File; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Map; @@ -205,6 +209,15 @@ public static void startPython(String... extraPaths) { } static native boolean startPython0(String... paths); + + /** + * Does the equivalent of setting the PYTHONHOME environment variable. If used, + * this must be called prior to calling {@code startPython()}. + * Supported for Python 2.7, and Python 3.5 or higher + * @param pythonHome Path to Python Home (must be less than 256 characters!) + * @return true if successful, false if it fails + */ + public static native boolean setPythonHome(String pythonHome); /** * @return The Python interpreter version string. @@ -232,7 +245,14 @@ public static void stopPython() { @Deprecated public static native int execScript(String script); - static native long executeCode(String code, int start, Map globals, Map locals); + static native long executeCode(String code, int start, Object globals, Object locals); + + static native long executeScript + (String file, int start, Object globals, Object locals) throws FileNotFoundException; + + public static native PyObject getMainGlobals(); + + static native PyObject copyDict(long pyPointer); static native void incRef(long pointer); @@ -240,12 +260,33 @@ public static void stopPython() { static native int getIntValue(long pointer); + static native boolean getBooleanValue(long pointer); + static native double getDoubleValue(long pointer); static native String getStringValue(long pointer); static native Object getObjectValue(long pointer); + static native boolean isConvertible(long pointer); + static native boolean pyNoneCheck(long pointer); + static native boolean pyDictCheck(long pointer); + static native boolean pyListCheck(long pointer); + static native boolean pyBoolCheck(long pointer); + static native boolean pyIntCheck(long pointer); + static native boolean pyLongCheck(long pointer); + static native boolean pyFloatCheck(long pointer); + static native boolean pyStringCheck(long pointer); + static native boolean pyCallableCheck(long pointer); + + static native long getType(long pointer); + + static native String str(long pointer); + + static native String repr(long pointer); + + static native PyObject newDict(); + static native T[] getObjectArrayValue(long pointer, Class itemType); static native long importModule(String name); @@ -284,6 +325,25 @@ public static void stopPython() { */ static native void setAttributeValue(long pointer, String name, T value, Class valueType); + /** + * Deletes the Python attribute given by {@code name} of the Python object pointed to by {@code pointer}. + *

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

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

diff --git a/src/main/java/org/jpy/PyListWrapper.java b/src/main/java/org/jpy/PyListWrapper.java new file mode 100644 index 0000000000..29068f7918 --- /dev/null +++ b/src/main/java/org/jpy/PyListWrapper.java @@ -0,0 +1,213 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +import java.util.*; + +/** + * A simple wrapper around a Python List object that implements a java List of PyObjects. + */ +class PyListWrapper implements List { + private PyObject pyObject; + + PyListWrapper(PyObject pyObject) { + this.pyObject = pyObject; + } + + @Override + public int size() { + return pyObject.callMethod("__len__").getIntValue(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + for (PyObject obj : this) { + if (obj.equals(o)) { + return true; + } + } + return false; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int ii = 0; + int size = size(); + + @Override + public boolean hasNext() { + return ii < size; + } + + @Override + public PyObject next() { + return get(ii++); + } + }; + } + + @Override + public PyObject[] toArray() { + int size = size(); + + PyObject [] result = new PyObject[size]; + for (int ii = 0; ii < size; ++ii) { + result[ii] = get(ii); + } + + return result; + } + + @Override + public T[] toArray(T[] a) { + int size = size(); + + if (a.length < size) { + a = Arrays.copyOf(a, size); + } + for (int ii = 0; ii < size; ++ii) { + //noinspection unchecked + a[ii] = (T)get(ii); + } + if (a.length > size) { + a[size] = null; + } + + return a; + } + + @Override + public boolean add(PyObject pyObject) { + pyObject.callMethod("append", pyObject); + return true; + } + + @Override + public boolean remove(Object o) { + try { + pyObject.callMethod("remove", pyObject); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean containsAll(Collection c) { + return c.stream().allMatch(this::contains); + } + + @Override + public boolean addAll(Collection c) { + boolean result = false; + for (PyObject po : c) { + result |= add(po); + } + return result; + } + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + pyObject.callMethod("clear"); + } + + @Override + public PyObject get(int index) { + return pyObject.callMethod("__getitem__", index); + } + + @Override + public PyObject set(int index, PyObject element) { + return pyObject.callMethod("__setitem__", index, element); + } + + @Override + public void add(int index, PyObject element) { + pyObject.callMethod("insert", index, element); + } + + @Override + public PyObject remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(Object o) { + int size = size(); + + for (int ii = 0; ii < size; ++ii) { + PyObject pyObject = get(ii); + if (pyObject == null ? o == null : pyObject.equals(o)) { + return ii; + } + } + + return -1; + } + + @Override + public int lastIndexOf(Object o) { + int size = size(); + + for (int ii = size - 1; ii >= 0; --ii) { + PyObject pyObject = get(ii); + if (pyObject == null ? o == null : pyObject.equals(o)) { + return ii; + } + } + + return -1; + } + + @Override + public ListIterator listIterator() { + throw new UnsupportedOperationException(); + } + + @Override + public ListIterator listIterator(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/jpy/PyModule.java b/src/main/java/org/jpy/PyModule.java index 9794cc7ae4..182a3b759a 100644 --- a/src/main/java/org/jpy/PyModule.java +++ b/src/main/java/org/jpy/PyModule.java @@ -16,6 +16,7 @@ package org.jpy; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; /** @@ -89,6 +90,7 @@ public static PyModule getBuiltins() { */ public static PyModule importModule(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.importModule(name); return pointer != 0 ? new PyModule(name, pointer) : null; } @@ -102,6 +104,7 @@ public static PyModule importModule(String name) { * @since 0.8 */ public static PyObject extendSysPath(String modulePath, boolean prepend) { + Objects.requireNonNull(modulePath, "path must not be null"); PyModule sys = importModule("sys"); PyObject sysPath = sys.getAttribute("path"); if (prepend) { @@ -121,8 +124,10 @@ public static PyObject extendSysPath(String modulePath, boolean prepend) { * @param The interface name. * @return A (proxy) instance implementing the given interface. */ + @Override public T createProxy(Class type) { assertPythonRuns(); + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.FUNCTION, type); } } diff --git a/src/main/java/org/jpy/PyObject.java b/src/main/java/org/jpy/PyObject.java index a50f52cacb..36212fa0d5 100644 --- a/src/main/java/org/jpy/PyObject.java +++ b/src/main/java/org/jpy/PyObject.java @@ -12,13 +12,18 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ package org.jpy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; -import java.util.Map; +import java.util.List; +import java.io.FileNotFoundException; +import java.util.Objects; import static org.jpy.PyLib.assertPythonRuns; @@ -54,6 +59,17 @@ public static PyObject executeCode(String code, PyInputMode mode) { return executeCode(code, mode, null, null); } + /** + * Executes Python source script. + * + * @param script The Python source script. + * @param mode The execution mode. + * @return The result of executing the script as a Python object. + */ + public static PyObject executeScript(String script, PyInputMode mode) throws FileNotFoundException { + return executeScript(script, mode, null, null); + } + /** * Executes Python source code in the context specified by the {@code globals} and {@code locals} maps. *

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

+ * If a Java value in the {@code globals} and {@code locals} maps cannot be directly converted into a Python object, a Java wrapper will be created instead. + * If a Java value is a wrapped Python object of type {@link PyObject}, it will be unwrapped. + * + * @param script The Python source script. + * @param mode The execution mode. + * @param globals The global variables to be set, or {@code null}. + * @param locals The locals variables to be set, or {@code null}. + * @return The result of executing the script as a Python object. + * @throws FileNotFoundException if the script file is not found + */ + public static PyObject executeScript(String script, PyInputMode mode, Object globals, Object locals) throws FileNotFoundException { + Objects.requireNonNull(script, "script must not be null"); + Objects.requireNonNull(mode, "mode must not be null"); + return new PyObject(PyLib.executeScript(script, mode.value(), globals, locals)); + } + /** * Decrements the reference count of the Python object which this class represents. * @@ -106,6 +138,14 @@ public int getIntValue() { return PyLib.getIntValue(getPointer()); } + /** + * @return This Python object as a Java {@code boolean} value. + */ + public boolean getBooleanValue() { + assertPythonRuns(); + return PyLib.getBooleanValue(getPointer()); + } + /** * @return This Python object as a Java {@code double} value. */ @@ -135,6 +175,80 @@ public Object getObjectValue() { return PyLib.getObjectValue(getPointer()); } + /** + * Gets the Python type object for this wrapped object. + * + * @return This Python object's type as a {@code PyObject} wrapped value. + */ + public PyObject getType() { + assertPythonRuns(); + return new PyObject(PyLib.getType(getPointer())); + } + + public boolean isDict() { + assertPythonRuns(); + return PyLib.pyDictCheck(getPointer()); + } + + public boolean isList() { + assertPythonRuns(); + return PyLib.pyListCheck(getPointer()); + } + + public boolean isBoolean() { + assertPythonRuns(); + return PyLib.pyBoolCheck(getPointer()); + } + + public boolean isLong() { + assertPythonRuns(); + return PyLib.pyLongCheck(getPointer()); + } + + public boolean isInt() { + assertPythonRuns(); + return PyLib.pyIntCheck(getPointer()); + } + + public boolean isNone() { + assertPythonRuns(); + return PyLib.pyNoneCheck(getPointer()); + } + + public boolean isFloat() { + assertPythonRuns(); + return PyLib.pyFloatCheck(getPointer()); + } + + public boolean isCallable() { + assertPythonRuns(); + return PyLib.pyCallableCheck(getPointer()); + } + + public boolean isString() { + assertPythonRuns(); + return PyLib.pyStringCheck(getPointer()); + } + + public boolean isConvertible() { + assertPythonRuns(); + return PyLib.isConvertible(getPointer()); + } + + public List asList() { + if (!isList()) { + throw new ClassCastException("Can not convert non-list type to a list!"); + } + return new PyListWrapper(this); + } + + public PyDictWrapper asDict() { + if (!isDict()) { + throw new ClassCastException("Can not convert non-list type to a dictionary!"); + } + return new PyDictWrapper(this); + } + /** * Gets this Python object as Java {@code Object[]} value of the given item type. * Appropriate type conversions from Python to Java will be performed as needed. @@ -149,6 +263,7 @@ public Object getObjectValue() { */ public T[] getObjectArrayValue(Class itemType) { assertPythonRuns(); + Objects.requireNonNull(itemType, "itemType must not be null"); return PyLib.getObjectArrayValue(getPointer(), itemType); } @@ -163,6 +278,7 @@ public T[] getObjectArrayValue(Class itemType) { */ public PyObject getAttribute(String name) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.getAttributeObject(getPointer(), name); return pointer != 0 ? new PyObject(pointer) : null; } @@ -175,12 +291,13 @@ public PyObject getAttribute(String name) { * If the Python value is a wrapped Java object, it will be unwrapped. * * @param name A name of the Python attribute. - * @param valueType The type of the value or {@code null}, if unknown + * @param valueType The type of the value or {@code null}, if unknown. * @param The expected value type name. * @return The Python attribute value as Java object. */ public T getAttribute(String name, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); return PyLib.getAttributeValue(getPointer(), name, valueType); } @@ -191,14 +308,38 @@ public T getAttribute(String name, Class valueType) { * If the Java {@code value} is a wrapped Python object of type {@link PyObject}, it will be unwrapped. * * @param name A name of the Python attribute. - * @param value The new attribute value as Java object. + * @param value The new attribute value as Java object. May be {@code null}. * @param The value type name. */ public void setAttribute(String name, T value) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, value != null ? value.getClass() : null); } + /** + * Deletes the value of a Python attribute. + * + * @param name the name of the Python attribute. + */ + public void delAttribute(String name) { + assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); + PyLib.delAttribute(getPointer(), name); + } + + /** + * Checks for the existence of a Python attribute.. + * + * @param name the name of the Python attribute. + * @return whether this attribute exists for this object + */ + public boolean hasAttribute(String name) { + assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); + return PyLib.hasAttribute(getPointer(), name); + } + /** * Sets the value of a Python attribute from the given Java object and Java type (if given). * Appropriate type conversions from Java to Python will be performed using the given value type. @@ -213,6 +354,7 @@ public void setAttribute(String name, T value) { */ public void setAttribute(String name, T value, Class valueType) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); PyLib.setAttributeValue(getPointer(), name, value, valueType); } @@ -228,6 +370,7 @@ public void setAttribute(String name, T value, Class valueType) */ public PyObject callMethod(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), true, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -244,6 +387,7 @@ public PyObject callMethod(String name, Object... args) { */ public PyObject call(String name, Object... args) { assertPythonRuns(); + Objects.requireNonNull(name, "name must not be null"); long pointer = PyLib.callAndReturnObject(getPointer(), false, name, args.length, args, null); return pointer != 0 ? new PyObject(pointer) : null; } @@ -259,6 +403,7 @@ public PyObject call(String name, Object... args) { public T createProxy(Class type) { assertPythonRuns(); //noinspection unchecked + Objects.requireNonNull(type, "type must not be null"); return (T) createProxy(PyLib.CallableKind.METHOD, type); } @@ -278,14 +423,24 @@ public Object createProxy(PyLib.CallableKind callableKind, Class... types) { } /** - * Gets a string representation of the object using the format "PyObject(pointer=<value>)". + * Gets the python string representation of this object. * * @return A string representation of the object. * @see #getPointer() */ @Override public final String toString() { - return String.format("%s(pointer=0x%s)", getClass().getSimpleName(), Long.toHexString(pointer)); + return PyLib.str(pointer); + } + + /** + * Gets a the python repr of this object + * + * @return A string representation of the object. + * @see #getPointer() + */ + public final String repr() { + return PyLib.repr(pointer); } /** diff --git a/src/main/java/org/jpy/StopIteration.java b/src/main/java/org/jpy/StopIteration.java new file mode 100644 index 0000000000..1e5451b95f --- /dev/null +++ b/src/main/java/org/jpy/StopIteration.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ +package org.jpy; + +/** + * Translation of Python StopIteration so that they can be programmatically detected from Java. + */ +public class StopIteration extends RuntimeException { + StopIteration(String message) { + super(message); + } +} diff --git a/src/test/java/org/jpy/PyLibTest.java b/src/test/java/org/jpy/PyLibTest.java index 629e9c4f17..3144e36d20 100644 --- a/src/test/java/org/jpy/PyLibTest.java +++ b/src/test/java/org/jpy/PyLibTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.*; +import java.util.Map; + public class PyLibTest { @Before @@ -141,4 +143,37 @@ public void testCallAndReturnObject() throws Exception { //PyLib.Diag.setFlags(PyLib.Diag.F_ALL); assertEquals("Z", new PyObject(pointer).getStringValue()); } + + @Test + public void testGetMainGlobals() throws Exception { + PyObject globals = PyLib.getMainGlobals(); + Map dict = globals.asDict(); + assertFalse(dict.isEmpty()); + + boolean foundName = false; + + for (Map.Entry entry : dict.entrySet()) { + if (entry.getKey().isString()) { + if (entry.getKey().getObjectValue().equals("__name__")) { + foundName = true; + break; + } + } + } + + assertTrue(foundName); + } + + @Test + public void testNewDict() throws Exception { + PyObject dict = PyLib.newDict(); + assertTrue(dict.asDict().isEmpty()); + + PyObject globals = PyLib.getMainGlobals(); + + PyObject.executeCode("x = 42", PyInputMode.STATEMENT, globals, dict); + + assertFalse(dict.asDict().isEmpty()); + } + } diff --git a/src/test/java/org/jpy/PyObjectTest.java b/src/test/java/org/jpy/PyObjectTest.java index b3e3fd880c..8d2d530756 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; @@ -23,6 +26,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.Map; import java.util.List; import java.util.concurrent.*; @@ -66,7 +70,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 @@ -128,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")); @@ -137,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 @@ -189,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(); @@ -226,6 +279,10 @@ static void testCallProxySingleThreaded(PyObject procObject) { assertEquals("computeTile-100,200", result); result = processor.computeTile(200, 200, new float[100 * 100]); assertEquals("computeTile-200,200", result); + processor.setVal(1234); + int val = processor.getVal(); + assertEquals(val, 1234); + assertEquals(true, processor.check1234()); result = processor.dispose(); assertEquals("dispose", result); } diff --git a/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java b/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java new file mode 100644 index 0000000000..f2a3971266 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/CovariantOverloadExtendTestFixture.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Charles P. Wright + */ +@SuppressWarnings("UnusedDeclaration") +public class CovariantOverloadExtendTestFixture extends CovariantOverloadTestFixture { + public CovariantOverloadExtendTestFixture(int x) { + super(x * 2); + } + + public CovariantOverloadExtendTestFixture foo(Number a, int b) { + return new CovariantOverloadExtendTestFixture(a.intValue() - b); + } +} \ No newline at end of file diff --git a/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java b/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java new file mode 100644 index 0000000000..5d1db8afd3 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/CovariantOverloadTestFixture.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Charles P. Wright + */ +@SuppressWarnings("UnusedDeclaration") +public class CovariantOverloadTestFixture { + int x; + + public CovariantOverloadTestFixture(int x) { + this.x = x; + } + + public CovariantOverloadTestFixture foo(Number a, int b) { + return new CovariantOverloadTestFixture(a.intValue() + b); + } + + public int getX() { + return x; + } +} \ No newline at end of file diff --git a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java index cec174ad66..ac58beed71 100644 --- a/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java +++ b/src/test/java/org/jpy/fixtures/ExceptionTestFixture.java @@ -12,6 +12,9 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * + * This file was modified by Illumon. + * */ package org.jpy.fixtures; @@ -29,6 +32,30 @@ public int throwNpeIfArgIsNull(String arg) { return arg.length(); } + public int throwNpeIfArgIsNull2(String arg) { + return throwNpeIfArgIsNull(arg); + } + + public int throwNpeIfArgIsNullNested(String arg) { + try { + return throwNpeIfArgIsNull(arg); + } catch (Exception e) { + throw new RuntimeException("Nested exception", e); + } + } + + public int throwNpeIfArgIsNullNested2(String arg) { + return throwNpeIfArgIsNullNested(arg); + } + + public int throwNpeIfArgIsNullNested3(String arg) { + try { + return throwNpeIfArgIsNullNested2(arg); + } catch (Exception e) { + throw new RuntimeException("Nested exception 3", e); + } + } + public int throwAioobeIfIndexIsNotZero(int index) { int[] ints = new int[]{101}; return ints[index]; diff --git a/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java b/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java index ed4455e400..4d07891bf0 100644 --- a/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java +++ b/src/test/java/org/jpy/fixtures/MethodOverloadTestFixture.java @@ -72,6 +72,16 @@ public String join(String a, String b, String c) { return stringifyArgs(a, b, c); } + ////////////////////////////////////////////// + public String join2(Comparable a, int b, String c, String d) { + return stringifyArgs(a, b, c, d); + } + + ////////////////////////////////////////////// + public String join3(Number a, int b) { + return stringifyArgs(a, b); + } + /** * Used to test that we also find overloaded methods in class hierarchies */ diff --git a/src/test/java/org/jpy/fixtures/Processor.java b/src/test/java/org/jpy/fixtures/Processor.java index 29332fc372..08835f88d8 100644 --- a/src/test/java/org/jpy/fixtures/Processor.java +++ b/src/test/java/org/jpy/fixtures/Processor.java @@ -27,4 +27,10 @@ public interface Processor { String computeTile(int w, int h, float[] data); String dispose(); + + void setVal(int n); + + int getVal(); + + boolean check1234(); } diff --git a/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java new file mode 100644 index 0000000000..f52b00919f --- /dev/null +++ b/src/test/java/org/jpy/fixtures/TypeTranslationTestFixture.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ + +package org.jpy.fixtures; + +/** + * Used as a test class for the test cases in jpy_retval_test.py + * Note: Please make sure to not add any method overloads to this class. + * This is done in {@link MethodOverloadTestFixture}. + * + * @author Norman Fomferra + */ +@SuppressWarnings("UnusedDeclaration") +public class TypeTranslationTestFixture { + public Thing makeThing(int value) { + return new Thing(value); + } +} diff --git a/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java new file mode 100644 index 0000000000..230fdf2008 --- /dev/null +++ b/src/test/java/org/jpy/fixtures/VarArgsTestFixture.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 Brockmann Consult GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file was modified by Illumon. + * + */ + +package org.jpy.fixtures; + +import java.lang.reflect.Array; + +/** + * Used as a test class for the test cases in jpy_overload_test.py + * + * @author Norman Fomferra + */ +@SuppressWarnings("UnusedDeclaration") +public class VarArgsTestFixture { + + public String join(String prefix, int ... a) { + return stringifyArgs(prefix, a); + } + + public String join(String prefix, double ... a) { + return stringifyArgs(prefix, a); + } + public String join(String prefix, float ... a) { + return stringifyArgs(prefix, a); + } + public String join(String prefix, String ... a) { + return stringifyArgs(prefix, a); + } + + public String joinFloat(String prefix, float ... a) { + return stringifyArgs(prefix, a); + } + + public String joinLong(String prefix, long ... a) { + return stringifyArgs(prefix, a); + } + public String joinShort(String prefix, short ... a) { + return stringifyArgs(prefix, a); + } + public String joinByte(String prefix, byte ... a) { + return stringifyArgs(prefix, a); + } + public String joinChar(String prefix, char ... a) { + return stringifyArgs(prefix, a); + } + public String joinBoolean(String prefix, boolean ... a) { + return stringifyArgs(prefix, a); + } + public String joinObjects(String prefix, Object ... a) { + return stringifyArgs(prefix, a); + } + + public int chooseFixedArity(int... a) { + return 2; + } + + public int chooseFixedArity() { + return 1; + } + + public int stringOrObjectVarArgs(String ... a) { + return 1 + a.length; + } + public int stringOrObjectVarArgs(Object ... a) { + return 2 + a.length; + } + + static String stringifyArgs(Object... args) { + StringBuilder argString = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + argString.append(","); + } + Object arg = args[i]; + if (arg != null) { + Class argClass = arg.getClass(); + argString.append(argClass.getSimpleName()); + argString.append('('); + if (argClass.isArray()) { + stringifyArray(arg, argString); + } else { + stringifyObject(arg, argString); + } + argString.append(')'); + } else { + argString.append("null"); + } + } + return argString.toString(); + } + + private static void stringifyObject(Object arg, StringBuilder argString) { + argString.append(String.valueOf(arg)); + } + + private static void stringifyArray(Object arg, StringBuilder argString) { + boolean primitive = arg.getClass().getComponentType().isPrimitive(); + int length = Array.getLength(arg); + for (int i = 0; i < length; i++) { + Object item = Array.get(arg, i); + if (i > 0) { + argString.append(","); + } + if (primitive) { + argString.append(String.valueOf(item)); + } else { + argString.append(stringifyArgs(item)); + } + } + } + + +} diff --git a/src/test/python/fixtures/proc_class.py b/src/test/python/fixtures/proc_class.py index ce61510d8c..8e4e20df68 100644 --- a/src/test/python/fixtures/proc_class.py +++ b/src/test/python/fixtures/proc_class.py @@ -19,5 +19,12 @@ def spend_some_time(self): for i in range(10000): l.reverse() - - + def setVal(self, val): + self._val = val + + def getVal(self): + return self._val + + def check1234(self): + return self._val == 1234 + \ No newline at end of file diff --git a/src/test/python/fixtures/proc_module.py b/src/test/python/fixtures/proc_module.py index 63e633c4af..01f75e6b12 100644 --- a/src/test/python/fixtures/proc_module.py +++ b/src/test/python/fixtures/proc_module.py @@ -15,6 +15,15 @@ def spend_some_time(): for i in range(10000): l.reverse() - - - +_module_val = None +def setVal(val): + global _module_val + _module_val = val + +def getVal(): + global _module_val + return _module_val + +def check1234(): + global _module_val + return _module_val == 1234 diff --git a/src/test/python/jpy_exception_test.py b/src/test/python/jpy_exception_test.py index d5bd20c249..3888aeee0c 100644 --- a/src/test/python/jpy_exception_test.py +++ b/src/test/python/jpy_exception_test.py @@ -1,12 +1,12 @@ +# This file was modified by Illumon. import unittest import jpyutil - +import string jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) import jpy - class TestExceptions(unittest.TestCase): def setUp(self): self.Fixture = jpy.get_type('org.jpy.fixtures.ExceptionTestFixture') @@ -53,6 +53,44 @@ def test_IOException(self): fixture.throwIoeIfMessageIsNotNull("Evil!") self.assertEqual(str(e.exception), 'java.io.IOException: Evil!') + # Checking the exceptions for differences (e.g. in white space) can be a huge pain, this helps) + def hexdump(self, s): + for i in xrange(0, len(s), 32): + sl = s[i:min(i + 32, len(s))] + fsl = map(lambda x : x if (x in string.printable and not x in "\n\t\r") else ".", sl) + print "%08d %s %s %s" % (i, " ".join("{:02x}".format(ord(c)) for c in sl), (" ".join(map(lambda x : "", xrange(32 - len(sl))))), sl) + + def test_VerboseException(self): + fixture = self.Fixture() + + jpy.VerboseExceptions.enabled = True + + self.assertEqual(fixture.throwNpeIfArgIsNull("123456"), 6) + + with self.assertRaises(RuntimeError) as e: + fixture.throwNpeIfArgIsNullNested(None) + actualMessage = str(e.exception) + expectedMessage = "java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:43)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:32)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:41)\n" + + #self.hexdump(actualMessage) + #self.hexdump(expectedMessage) + #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] + + self.assertEquals(actualMessage, expectedMessage) + + with self.assertRaises(RuntimeError) as e: + fixture.throwNpeIfArgIsNullNested3(None) + actualMessage = str(e.exception) + expectedMessage = "java.lang.RuntimeException: Nested exception 3\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:55)\ncaused by java.lang.RuntimeException: Nested exception\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:43)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested2(ExceptionTestFixture.java:48)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested3(ExceptionTestFixture.java:53)\ncaused by java.lang.NullPointerException\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNull(ExceptionTestFixture.java:32)\n\tat org.jpy.fixtures.ExceptionTestFixture.throwNpeIfArgIsNullNested(ExceptionTestFixture.java:41)\n\t... 2 more\n" + + #self.hexdump(actualMessage) + #self.hexdump(expectedMessage) + #print [i for i in xrange(min(len(expectedMessage), len(actualMessage))) if actualMessage[i] != expectedMessage[i]] + + self.assertEquals(actualMessage, expectedMessage) + + jpy.VerboseExceptions.enabled = False + if __name__ == '__main__': print('\nRunning ' + __file__) diff --git a/src/test/python/jpy_overload_test.py b/src/test/python/jpy_overload_test.py index 4a7c774418..b9be7a1d72 100644 --- a/src/test/python/jpy_overload_test.py +++ b/src/test/python/jpy_overload_test.py @@ -1,3 +1,4 @@ +# This file was modified by Illumon. import unittest import jpyutil @@ -84,16 +85,82 @@ def test_nArgOverloadsAreFoundInBaseClass(self): fixture.join('x', 'y', 'z', 'u', 'v') self.assertEqual(str(e.exception), 'no matching Java method overloads found') + def test_stringAsComparable(self): + fixture = self.Fixture() + self.assertEqual(fixture.join2("a", 1, "c", "d"), 'String(a),Integer(1),String(c),String(d)') + + def test_stringAsNumber(self): + fixture = self.Fixture() + with self.assertRaises(RuntimeError, msg='RuntimeError expected') as e: + fixture.join3('x', 2) + self.assertEqual(str(e.exception), 'no matching Java method overloads found') + + def test_numbersAsNumber(self): + fixture = self.Fixture() + self.assertEqual(fixture.join3(1, 2), 'Integer(1),Integer(2)') + self.assertEqual(fixture.join3(1.1, 2), 'Double(1.1),Integer(2)') + + def test_numbersAsComparable(self): + fixture = self.Fixture() + self.assertEqual(fixture.join2(1, 2, "c", "d"), 'Integer(1),Integer(2),String(c),String(d)') + self.assertEqual(fixture.join2(1.1, 2, "c", "d"), 'Double(1.1),Integer(2),String(c),String(d)') + +class TestVarArgs(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.VarArgsTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_varargsEmpty(self): + fixture = self.Fixture() + + self.assertEqual(fixture.joinFloat("Prefix"), 'String(Prefix),float[]()') + + with self.assertRaises(RuntimeError, msg='RuntimeError expected') as e: + res = fixture.join("Prefix") + self.assertEqual(str(e.exception), 'ambiguous Java method call, too many matching method overloads found') + + def test_varargs(self): + fixture = self.Fixture() + + self.assertEqual(fixture.join("Prefix", "a", "b", "c"), 'String(Prefix),String[](String(a),String(b),String(c))') + self.assertEqual(fixture.join("Prefix", 1, 2, 3), 'String(Prefix),int[](1,2,3)') + self.assertEqual(fixture.join("Prefix", 1.1, 2.1, 3.1), 'String(Prefix),double[](1.1,2.1,3.1)') + + self.assertEqual(fixture.joinFloat("Prefix", 1.1, 2.1, 3.1), 'String(Prefix),float[](1.1,2.1,3.1)') + + self.assertEqual(fixture.joinLong("Prefix", 1, 2, 3), 'String(Prefix),long[](1,2,3)') + bignum = 8589934592 + self.assertEqual(fixture.joinLong("Prefix", 1, 2, 3, bignum), 'String(Prefix),long[](1,2,3,'+str(bignum)+')') + + self.assertEqual(fixture.joinByte("Prefix", 1, 2, 3), 'String(Prefix),byte[](1,2,3)') + self.assertEqual(fixture.joinShort("Prefix", 1, 2, 3, 4), 'String(Prefix),short[](1,2,3,4)') + self.assertEqual(fixture.joinChar("Prefix", 65, 66), 'String(Prefix),char[](A,B)') + + self.assertEqual(fixture.joinBoolean("Prefix", True, False), 'String(Prefix),boolean[](true,false)') + self.assertEqual(fixture.joinObjects("Prefix", True, "A String", 3), 'String(Prefix),Object[](Boolean(true),String(A String),Integer(3))') + + def test_fixedArity(self): + fixture = self.Fixture() + + self.assertEqual(fixture.chooseFixedArity(), 1) + self.assertEqual(fixture.chooseFixedArity(1), 2) + self.assertEqual(fixture.chooseFixedArity(1, 2), 2) + + def test_stringVsObject(self): + fixture = self.Fixture() + self.assertEqual(fixture.stringOrObjectVarArgs(["a", "b"]), 3) + self.assertEqual(fixture.stringOrObjectVarArgs([1, 2, 3]), 5) + + class TestOtherMethodResolutionCases(unittest.TestCase): # see https://github.com/bcdev/jpy/issues/55 def test_toReproduceAndFixIssue55(self): Paths = jpy.get_type('java.nio.file.Paths') - # The following outcommented statement is will end in a Python error - # RuntimeError: no matching Java method overloads found - #p = Paths.get('testfile') - # This is the embarrassing workaround + # The following statement will execute the var args method without any arguments + p = Paths.get('testfile') + # This is the workaround that was previously required p = Paths.get('testfile', []) # see https://github.com/bcdev/jpy/issues/56 @@ -136,10 +203,20 @@ def setUp(self): # see https://github.com/bcdev/jpy/issues/102 def test_defaultedInterfaces(self): - fixture = self.Fixture() + fixture = self.Fixture() self.assertEqual(fixture.doItPlusOne(), 3) +class TestCovariantReturn(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.CovariantOverloadExtendTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_covariantReturn(self): + fixture = self.Fixture(1) + self.assertEqual(fixture.foo(4, 1).getX(), 6) + + if __name__ == '__main__': print('\nRunning ' + __file__) unittest.main() diff --git a/src/test/python/jpy_translation_test.py b/src/test/python/jpy_translation_test.py new file mode 100644 index 0000000000..ca64cf47da --- /dev/null +++ b/src/test/python/jpy_translation_test.py @@ -0,0 +1,42 @@ +# This file was modified by Illumon. +import unittest + +import jpyutil + +jpyutil.init_jvm(jvm_maxmem='512M', jvm_classpath=['target/test-classes']) +import jpy + +class DummyWrapper: + def __init__(self, theThing): + self.theThing = theThing + + def getValue(self): + return 2 * self.theThing.getValue() + +def make_wrapper(type, thing): + return DummyWrapper(thing) + + +class TestTypeTranslation(unittest.TestCase): + def setUp(self): + self.Fixture = jpy.get_type('org.jpy.fixtures.TypeTranslationTestFixture') + self.assertIsNotNone(self.Fixture) + + def test_Translation(self): + fixture = self.Fixture() + thing = fixture.makeThing(7) + self.assertEqual(thing.getValue(), 7) + self.assertEquals(repr(type(thing)), "") + + jpy.type_translations['org.jpy.fixtures.Thing'] = make_wrapper + thing = fixture.makeThing(8) + self.assertEqual(thing.getValue(), 16) + self.assertEqual(type(thing), type(DummyWrapper(None))) + + jpy.type_translations['org.jpy.fixtures.Thing'] = None + self.assertEqual(fixture.makeThing(9).getValue(), 9) + + +if __name__ == '__main__': + print('\nRunning ' + __file__) + unittest.main()