From: tuorjr Date: Tue, 4 Oct 2016 16:22:17 +0000 (+0000) Subject: Added support for dynamically typed data access and a Variable interface. X-Git-Tag: v1.31.0~14 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=15304b7c3168ef89c47dcfcfb4cf34c72000a75f;p=simantics%2Fpython.git Added support for dynamically typed data access and a Variable interface. Note: Proper function of the Variable interface depends on enhancement #6738. git-svn-id: https://www.simantics.org/svn/simantics-incubator/reino@1694 e36c2e66-7d30-0410-bdb2-d9e1f5a6d952 --- diff --git a/org.simantics.pythonlink.win32.x86_64/jnipython.dll b/org.simantics.pythonlink.win32.x86_64/jnipython.dll index 1960f88..fb4cf4a 100644 Binary files a/org.simantics.pythonlink.win32.x86_64/jnipython.dll and b/org.simantics.pythonlink.win32.x86_64/jnipython.dll differ diff --git a/org.simantics.pythonlink.win32.x86_64/src/sclpy.c b/org.simantics.pythonlink.win32.x86_64/src/sclpy.c index 4f5da9b..0db1e38 100644 --- a/org.simantics.pythonlink.win32.x86_64/src/sclpy.c +++ b/org.simantics.pythonlink.win32.x86_64/src/sclpy.c @@ -10,33 +10,10 @@ // // /////////////////////////////////////////////////////// -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -#ifdef _DEBUG - #undef _DEBUG - #include //header for system python import; add include paths - #include - #define _DEBUG 1 -#else - #include //header for system python import; add include paths - #include -#endif - #include "sclpy.h" -#include //java connection header - #include -#define JAVA_MAXINT (0x7fffffff) - -#define RUNTIME_EXCEPTION "java/lang/RuntimeException" -#define ILLEGAL_ARGUMENT_EXCEPTION "java/lang/IllegalArgumentException" -#define STRING_CLASS "java/lang/String" - -#define PACKAGE_PREFIX "org/simantics/pythonlink/" - -#define NDARRAY_CLASS (PACKAGE_PREFIX "NDArray") - jint throwException( JNIEnv *env, char *className, char *message ) { jclass exClass = (*env)->FindClass( env, className); @@ -52,9 +29,20 @@ jint throwIllegalArgumentException( JNIEnv *env, char *message ) { } int moduleCount = 0; +int initCalled = 0; +int hasNumpy = 0; JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_createContextImpl(JNIEnv *env, jobject thisObj) { char name[16]; + + if (!initCalled) { + Py_Initialize(); + initCalled = 1; + + hasNumpy = _import_array(); + hasNumpy = hasNumpy != -1; + } + sprintf(name, "SCL_%d", ++moduleCount); { @@ -62,6 +50,7 @@ JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_createContex PyObject *main = PyImport_AddModule("__main__"); PyDict_Merge(PyModule_GetDict(module), PyModule_GetDict(main), 0); + return (jlong)module; } } @@ -71,6 +60,15 @@ JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_deleteContext Py_XDECREF(module); } +PyObject *getPythonBool(jboolean value) { + if (value) { + Py_RETURN_TRUE; + } + else { + Py_RETURN_FALSE; + } +} + PyObject *getPythonString(JNIEnv *env, jstring string) { jsize len = (*env)->GetStringLength(env, string); const jchar *chars = (*env)->GetStringChars(env, string, NULL); @@ -102,6 +100,30 @@ PyObject *getPythonStringList(JNIEnv *env, jobjectArray value) { return result; } +PyObject *getPythonBooleanList(JNIEnv *env, jbooleanArray value) { + jsize nitems = (*env)->GetArrayLength(env, value); + jboolean *values = (*env)->GetBooleanArrayElements(env, value, NULL); + jint i; + + PyObject *result = PyList_New(nitems); + for (i = 0; i < nitems; i++) { + PyList_SetItem(result, i, getPythonBool(values[i])); + } + + (*env)->ReleaseBooleanArrayElements(env, value, values, JNI_ABORT); + return result; +} + +PyObject *getPythonByteArray(JNIEnv *env, jbyteArray value) { + jint len = (*env)->GetArrayLength(env, value); + jbyte *values = (*env)->GetByteArrayElements(env, value, NULL); + + PyObject *result = PyByteArray_FromStringAndSize(values, len); + + (*env)->ReleaseByteArrayElements(env, value, values, JNI_ABORT); + return result; +} + PyObject *getPythonIntegerList(JNIEnv *env, jintArray value) { jsize nitems = (*env)->GetArrayLength(env, value); jint *values = (*env)->GetIntArrayElements(env, value, NULL); @@ -116,6 +138,34 @@ PyObject *getPythonIntegerList(JNIEnv *env, jintArray value) { return result; } +PyObject *getPythonLongList(JNIEnv *env, jlongArray value) { + jsize nitems = (*env)->GetArrayLength(env, value); + jlong *values = (*env)->GetLongArrayElements(env, value, NULL); + jint i; + + PyObject *result = PyList_New(nitems); + for (i = 0; i < nitems; i++) { + PyList_SetItem(result, i, PyLong_FromLongLong(values[i])); + } + + (*env)->ReleaseLongArrayElements(env, value, values, JNI_ABORT); + return result; +} + +PyObject *getPythonFloatList(JNIEnv *env, jfloatArray value) { + jsize nitems = (*env)->GetArrayLength(env, value); + float *values = (*env)->GetFloatArrayElements(env, value, NULL); + jint i; + + PyObject *result = PyList_New(nitems); + for (i = 0; i < nitems; i++) { + PyList_SetItem(result, i, PyFloat_FromDouble((double)values[i])); + } + + (*env)->ReleaseFloatArrayElements(env, value, values, JNI_ABORT); + return result; +} + PyObject *getPythonDoubleList(JNIEnv *env, jdoubleArray value) { jsize nitems = (*env)->GetArrayLength(env, value); double *values = (*env)->GetDoubleArrayElements(env, value, NULL); @@ -168,6 +218,274 @@ PyObject *getPythonNDArray(JNIEnv *env, jobject value) { } } +PyObject *getPythonBooleanObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, BOOLEANBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)Z"); + + jboolean bvalue = (*env)->CallBooleanMethod(env, binding, getValueMethod, object); + return getPythonBool(bvalue); +} + +PyObject *getPythonByteObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, BYTEBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)B"); + + jbyte v = (*env)->CallByteMethod(env, binding, getValueMethod, object); + return PyLong_FromLong(v); +} + +PyObject *getPythonIntegerObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, INTEGERBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)I"); + + jint v = (*env)->CallIntMethod(env, binding, getValueMethod, object); + return PyLong_FromLong(v); +} + +PyObject *getPythonLongObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, LONGBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)J"); + + jlong v = (*env)->CallLongMethod(env, binding, getValueMethod, object); + return PyLong_FromLongLong(v); +} + +PyObject *getPythonFloatObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, FLOATBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)F"); + + jfloat v = (*env)->CallFloatMethod(env, binding, getValueMethod, object); + return PyFloat_FromDouble(v); +} + +PyObject *getPythonDoubleObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, DOUBLEBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue_", "(L" OBJECT_CLASS ";)D"); + + jdouble v = (*env)->CallDoubleMethod(env, binding, getValueMethod, object); + return PyFloat_FromDouble(v); +} + +PyObject *getPythonStringObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, STRINGBINDING_CLASS); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue", "(L" OBJECT_CLASS ";)L" STRING_CLASS ";"); + + jobject string = (*env)->CallObjectMethod(env, binding, getValueMethod, object); + jsize len = (*env)->GetStringLength(env, string); + const jchar *chars = (*env)->GetStringChars(env, string, NULL); + + PyObject *value = PyUnicode_DecodeUTF16((char*)chars, 2*len, NULL, NULL); + + (*env)->ReleaseStringChars(env, string, chars); + return value; +} + +PyObject *getPythonRecordObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, RECORDBINDING_CLASS); + jmethodID typeMethod = (*env)->GetMethodID(env, bindingClass, "type", "()L" RECORDTYPE_CLASS ";"); + jmethodID getComponent = (*env)->GetMethodID(env, bindingClass, "getComponent", "(L" OBJECT_CLASS ";I)L" OBJECT_CLASS ";"); + jmethodID getComponentBinding = (*env)->GetMethodID(env, bindingClass, "getComponentBinding", "(I)L" BINDING_CLASS ";"); + + jclass recordType = (*env)->FindClass(env, RECORDTYPE_CLASS); + jmethodID getTypeComponent = (*env)->GetMethodID(env, recordType, "getComponent", "(I)L" COMPONENT_CLASS ";"); + jmethodID getComponentCount = (*env)->GetMethodID(env, recordType, "getComponentCount", "()I"); + + jclass componentClass = (*env)->FindClass(env, COMPONENT_CLASS); + jfieldID nameField = (*env)->GetFieldID(env, componentClass, "name", "L" STRING_CLASS ";"); + + jobject type = (*env)->CallObjectMethod(env, binding, typeMethod); + jint n = (*env)->CallIntMethod(env, type, getComponentCount); + jint i; + + PyObject *result = PyDict_New(); + for (i = 0; i < n; i++) { + jobject recordTypeComponent = (*env)->CallObjectMethod(env, type, getComponent, i); + jstring fieldName = (jstring)(*env)->GetObjectField(env, recordTypeComponent, nameField); + jobject componentObject = (*env)->CallObjectMethod(env, binding, getComponent, object, i); + jobject componentBinding = (*env)->CallObjectMethod(env, binding, getComponentBinding, i); + + PyObject *item = getPythonObject(env, componentObject, componentBinding); + PyDict_SetItem(result, getPythonString(env, fieldName), item); + } + + return result; +} + +PyObject *getPythonArrayObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, ARRAYBINDING_CLASS); + jmethodID componentBindingMethod = (*env)->GetMethodID(env, bindingClass, "getComponentBinding", "()L" BINDING_CLASS ";"); + jmethodID sizeMethod = (*env)->GetMethodID(env, bindingClass, "size", "(L" OBJECT_CLASS ";)I"); + jmethodID getMethod = (*env)->GetMethodID(env, bindingClass, "get", "(L" OBJECT_CLASS ";I)L" OBJECT_CLASS ";"); + + jobject componentBinding = (*env)->CallObjectMethod(env, binding, componentBindingMethod); + + jint size = (*env)->CallIntMethod(env, binding, sizeMethod, object); + + PyObject *result = PyList_New(size); + + jint i; + for (i = 0; i < size; i++) { + jobject item = (*env)->CallObjectMethod(env, binding, getMethod, object, i); + if (item != NULL) + PyList_SetItem(result, i, getPythonObject(env, item, componentBinding)); + else + PyList_SetItem(result, i, Py_None); + } + + return result; +} + +PyObject *getPythonMapObject(JNIEnv *env, jobject object, jobject binding) { + jclass objectClass = (*env)->FindClass(env, OBJECT_CLASS); + jclass bindingClass = (*env)->FindClass(env, MAPBINDING_CLASS); + jmethodID getKeyBindingMethod = (*env)->GetMethodID(env, bindingClass, "getKeyBinding", "()L" BINDING_CLASS ";"); + jmethodID getValueBindingMethod = (*env)->GetMethodID(env, bindingClass, "getValueBinding", "()L" BINDING_CLASS ";"); + jmethodID sizeMethod = (*env)->GetMethodID(env, bindingClass, "size", "(L" OBJECT_CLASS ";)I"); + jmethodID getAllMethod = (*env)->GetMethodID(env, bindingClass, "getAll", "(L" OBJECT_CLASS ";[L" OBJECT_CLASS ";[L" OBJECT_CLASS ";)V"); + + jobject keyBinding = (*env)->CallObjectMethod(env, binding, getKeyBindingMethod); + jobject valueBinding = (*env)->CallObjectMethod(env, binding, getValueBindingMethod); + + jint size = (*env)->CallIntMethod(env, binding, sizeMethod, object); + jobjectArray keys = (*env)->NewObjectArray(env, size, objectClass, NULL); + jobjectArray values = (*env)->NewObjectArray(env, size, objectClass, NULL); + + PyObject *result = PyDict_New(); + jint i; + + (*env)->CallVoidMethod(env, binding, getAllMethod, object, keys, values); + + for (i = 0; i < size; i++) { + jobject key = (*env)->GetObjectArrayElement(env, keys, i); + jobject item = (*env)->GetObjectArrayElement(env, values, i); + PyDict_SetItem(result, getPythonObject(env, key, keyBinding), getPythonObject(env, item, valueBinding)); + } + + (*env)->DeleteLocalRef(env, keys); + (*env)->DeleteLocalRef(env, values); + + return result; +} + +PyObject *getPythonOptionalObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, OPTIONALBINDING_CLASS); + jmethodID hasValueMethod = (*env)->GetMethodID(env, bindingClass, "hasValue", "(L" OBJECT_CLASS ";)Z"); + + jboolean hasValue = (*env)->CallBooleanMethod(env, binding, hasValueMethod, object); + + if (hasValue) { + jmethodID componentBindingMethod = (*env)->GetMethodID(env, bindingClass, "getComponentBinding", "()L" BINDING_CLASS ";"); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "hasValue", "(L" OBJECT_CLASS ";)L" OBJECT_CLASS ";"); + + jobject componentBinding = (*env)->CallObjectMethod(env, binding, componentBindingMethod); + jobject value = (*env)->CallObjectMethod(env, binding, getValueMethod, object); + + return getPythonObject(env, value, componentBinding); + } + else { + return Py_None; + } +} + +PyObject *getPythonUnionObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, UNIONBINDING_CLASS); + jmethodID typeMethod = (*env)->GetMethodID(env, bindingClass, "type", "()L" RECORDTYPE_CLASS ";"); + jmethodID getTagMethod = (*env)->GetMethodID(env, bindingClass, "getTag", "(L" OBJECT_CLASS ";)I"); + jmethodID getValueMethod = (*env)->GetMethodID(env, bindingClass, "getValue", "(L" OBJECT_CLASS ";)L" OBJECT_CLASS ";"); + jmethodID getComponentBinding = (*env)->GetMethodID(env, bindingClass, "getComponentBinding", "(I)L" BINDING_CLASS ";"); + + jclass unionType = (*env)->FindClass(env, UNIONTYPE_CLASS); + jmethodID getTypeComponent = (*env)->GetMethodID(env, unionType, "getComponent", "(I)L" COMPONENT_CLASS ";"); + + jclass componentClass = (*env)->FindClass(env, COMPONENT_CLASS); + jfieldID nameField = (*env)->GetFieldID(env, componentClass, "name", "L" STRING_CLASS ";"); + + jint tag = (*env)->CallIntMethod(env, binding, getTagMethod, object); + jobject value = (*env)->CallObjectMethod(env, binding, getValueMethod, object); + + jobject type = (*env)->CallObjectMethod(env, binding, typeMethod); + jobject typeComponent = (*env)->CallObjectMethod(env, type, getTypeComponent, tag); + jstring compName = (*env)->GetObjectField(env, typeComponent, nameField); + + jobject componentBinding = (*env)->CallObjectMethod(env, binding, getComponentBinding, tag); + + PyObject *result = PyTuple_New(2); + PyTuple_SetItem(result, 0, getPythonString(env, compName)); + PyTuple_SetItem(result, 1, getPythonObject(env, value, componentBinding)); + + return result; +} + +PyObject *getPythonVariantObject(JNIEnv *env, jobject object, jobject binding) { + jclass bindingClass = (*env)->FindClass(env, VARIANTBINDING_CLASS); + jmethodID getContentMethod = (*env)->GetMethodID(env, bindingClass, "getContent", "(L" OBJECT_CLASS ";)L" OBJECT_CLASS ";"); + jmethodID getContentBindingMethod = (*env)->GetMethodID(env, bindingClass, "getContentBinding", "(L" OBJECT_CLASS ";)L" BINDING_CLASS ";"); + + jobject content = (*env)->CallObjectMethod(env, binding, getContentMethod, object); + jobject contentBinding = (*env)->CallObjectMethod(env, binding, getContentBindingMethod, object); + + return getPythonObject(env, content, contentBinding); +} + +PyObject *getPythonObject(JNIEnv *env, jobject value, jobject binding) { + jclass booleanBinding = (*env)->FindClass(env, BOOLEANBINDING_CLASS); + jclass byteBinding = (*env)->FindClass(env, BYTEBINDING_CLASS); + jclass integerBinding = (*env)->FindClass(env, INTEGERBINDING_CLASS); + jclass longBinding = (*env)->FindClass(env, LONGBINDING_CLASS); + jclass floatBinding = (*env)->FindClass(env, FLOATBINDING_CLASS); + jclass doubleBinding = (*env)->FindClass(env, DOUBLEBINDING_CLASS); + jclass stringBinding = (*env)->FindClass(env, STRINGBINDING_CLASS); + jclass recordBinding = (*env)->FindClass(env, RECORDBINDING_CLASS); + jclass arrayBinding = (*env)->FindClass(env, ARRAYBINDING_CLASS); + jclass mapBinding = (*env)->FindClass(env, MAPBINDING_CLASS); + jclass optionalBinding = (*env)->FindClass(env, OPTIONALBINDING_CLASS); + jclass untionBinding = (*env)->FindClass(env, UNIONBINDING_CLASS); + jclass variantBinding = (*env)->FindClass(env, VARIANTBINDING_CLASS); + + if ((*env)->IsInstanceOf(env, binding, booleanBinding)) { + return getPythonBooleanObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, byteBinding)) { + return getPythonByteObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, integerBinding)) { + return getPythonIntegerObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, longBinding)) { + return getPythonLongObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, floatBinding)) { + return getPythonFloatObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, doubleBinding)) { + return getPythonDoubleObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, stringBinding)) { + return getPythonStringObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, recordBinding)) { + return getPythonRecordObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, arrayBinding)) { + return getPythonArrayObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, mapBinding)) { + return getPythonMapObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, optionalBinding)) { + return getPythonOptionalObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, untionBinding)) { + return getPythonUnionObject(env, value, binding); + } + else if ((*env)->IsInstanceOf(env, binding, variantBinding)) { + return getPythonVariantObject(env, value, binding); + } + else { + return Py_None; + } +} + void setPythonVariable(PyObject *module, PyObject *name, PyObject *value) { if (name && value) { PyDict_SetItem(PyModule_GetDict(module), name, value); @@ -177,6 +495,62 @@ void setPythonVariable(PyObject *module, PyObject *name, PyObject *value) { Py_XDECREF(value); } +static npy_intp nContiguous(int d, int nd, npy_intp *strides, npy_intp *dims, npy_intp *ncont) { + if (d == nd) { + ncont[d] = 1; + return 1; + } + else { + npy_intp n = nContiguous(d+1, nd, strides, dims, ncont); + ncont[d] = n > 0 && strides[d] == sizeof(double) * n ? dims[d] * n : 0; + return ncont[d]; + } +} + +static void copyDoubleArrayValues(JNIEnv *env, jdoubleArray array, double *data, npy_intp *offset, int d, int nd, npy_intp *strides, npy_intp *dims, npy_intp *ncont) { + if (ncont[d] > 0) { + (*env)->SetDoubleArrayRegion(env, array, (jint)*offset, (jint)ncont[d], data); + *offset += ncont[d]; + } + else { + int i; + for (i = 0; i < dims[d]; i++) { + copyDoubleArrayValues(env, array, (double*)((char*)data + strides[d] * i), offset, d+1, nd, strides, dims, ncont); + } + } +} + +jobject pythonBoolAsBooleanObject(JNIEnv *env, PyObject *value) { + jclass booleanClass = (*env)->FindClass(env, "java/lang/Boolean"); + jmethodID valueOfMethod = (*env)->GetStaticMethodID(env, booleanClass, "valueOf", "(Z)Ljava/lang/Boolean;"); + + return (*env)->CallStaticObjectMethod(env, booleanClass, valueOfMethod, (jboolean)(value == Py_True)); +} + +jobject pythonLongAsLongObject(JNIEnv *env, PyObject *value) { + jclass longClass = (*env)->FindClass(env, "java/lang/Long"); + jmethodID valueOfMethod = (*env)->GetStaticMethodID(env, longClass, "valueOf", "(J)Ljava/lang/Long;"); + + return (*env)->CallStaticObjectMethod(env, longClass, valueOfMethod, PyLong_AsLongLong(value)); +} + +jobject pythonFloatAsDoubleObject(JNIEnv *env, PyObject *value) { + jclass doubleClass = (*env)->FindClass(env, "java/lang/Double"); + jmethodID valueOfMethod = (*env)->GetStaticMethodID(env, doubleClass, "valueOf", "(D)Ljava/lang/Double;"); + + return (*env)->CallStaticObjectMethod(env, doubleClass, valueOfMethod, PyFloat_AsDouble(value)); +} + +jobject pythonByteArrayAsByteArray(JNIEnv *env, PyObject *value) { + Py_ssize_t size = PyByteArray_Size(value); + jbyteArray result = (*env)->NewByteArray(env, (jsize)size); + char *bytes = PyByteArray_AsString(value); + + (*env)->SetByteArrayRegion(env, result, 0, size, bytes); + + return result; +} + jstring pythonStringAsJavaString(JNIEnv *env, PyObject *string) { PyObject *utf16Value = PyUnicode_AsUTF16String(string); Py_ssize_t len = PyBytes_Size(utf16Value) / 2; @@ -190,15 +564,15 @@ jstring pythonStringAsJavaString(JNIEnv *env, PyObject *string) { return result; } -jobjectArray pythonStringListAsJavaArray(JNIEnv *env, PyObject *list) { - Py_ssize_t len = PyList_Size(list); +jobjectArray pythonSequenceAsStringArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); jsize jlen = (jsize)min(len, JAVA_MAXINT); jobjectArray array = (*env)->NewObjectArray(env, jlen, (*env)->FindClass(env, STRING_CLASS), NULL); jint i; for (i = 0; i < jlen; i++) { - PyObject *item = PyList_GetItem(list, i); + PyObject *item = PySequence_GetItem(seq, i); if (PyUnicode_Check(item)) { jstring value = pythonStringAsJavaString(env, item); (*env)->SetObjectArrayElement(env, array, i, value); @@ -212,15 +586,15 @@ jobjectArray pythonStringListAsJavaArray(JNIEnv *env, PyObject *list) { return array; } -jdoubleArray pythonListAsDoubleArray(JNIEnv *env, PyObject *list) { - Py_ssize_t len = PyList_Size(list); +jdoubleArray pythonSequenceAsDoubleArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); jsize jlen = (jsize)min(len, JAVA_MAXINT); jdoubleArray array = (*env)->NewDoubleArray(env, jlen); jint i; for (i = 0; i < jlen; i++) { - PyObject *item = PyList_GetItem(list, i); + PyObject *item = PySequence_GetItem(seq, i); if (PyFloat_Check(item)) { double value = PyFloat_AsDouble(item); (*env)->SetDoubleArrayRegion(env, array, i, 1, &value); @@ -234,29 +608,107 @@ jdoubleArray pythonListAsDoubleArray(JNIEnv *env, PyObject *list) { return array; } -npy_intp nContiguous(int d, int nd, npy_intp *strides, npy_intp *dims, npy_intp *ncont) { - if (d == nd) { - ncont[d] = 1; - return 1; +jobject pythonObjectAsObject(JNIEnv *env, PyObject *value) { + if (PyBool_Check(value)) + return pythonBoolAsBooleanObject(env, value); + else if (PyLong_Check(value)) + return pythonLongAsLongObject(env, value); + else if (PyFloat_Check(value)) + return pythonFloatAsDoubleObject(env, value); + else if (PyUnicode_Check(value)) + return pythonStringAsJavaString(env, value); + else if (PyByteArray_Check(value)) + return pythonByteArrayAsByteArray(env, value); + else if (PyDict_Check(value)) + return pythonDictionaryAsMap(env, value); + else if (hasNumpy && PyArray_Check(value)) + return pythonArrayAsNDArray(env, (PyArrayObject *)value); + else if (PySequence_Check(value)) + return pythonSequenceAsObjectArray(env, value); + else + return NULL; +} + +jobjectArray pythonSequenceAsObjectArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); + jsize jlen = (jsize)min(len, JAVA_MAXINT); + jobjectArray array = (*env)->NewObjectArray(env, jlen, (*env)->FindClass(env, OBJECT_CLASS), NULL); + + jint i; + + for (i = 0; i < jlen; i++) { + PyObject *item = PySequence_GetItem(seq, i); + jobject object = pythonObjectAsObject(env, item); + (*env)->SetObjectArrayElement(env, array, i, object); } - else { - npy_intp n = nContiguous(d+1, nd, strides, dims, ncont); - ncont[d] = n > 0 && strides[d] == sizeof(double) * n ? dims[d] * n : 0; - return ncont[d]; + + return array; +} + +jbooleanArray pythonSequenceAsBooleanArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); + jsize jlen = (jsize)min(len, JAVA_MAXINT); + jbooleanArray array = (*env)->NewBooleanArray(env, jlen); + + jint i; + + for (i = 0; i < jlen; i++) { + PyObject *item = PySequence_GetItem(seq, i); + if (PyBool_Check(item)) { + jboolean value = item == Py_True; + (*env)->SetBooleanArrayRegion(env, array, i, 1, &value); + } + else { + throwException(env, RUNTIME_EXCEPTION, "List item not a boolean"); + return NULL; + } } + + return array; } -void copyDoubleArrayValues(JNIEnv *env, jdoubleArray array, double *data, npy_intp *offset, int d, int nd, npy_intp *strides, npy_intp *dims, npy_intp *ncont) { - if (ncont[d] > 0) { - (*env)->SetDoubleArrayRegion(env, array, (jint)*offset, (jint)ncont[d], data); - *offset += ncont[d]; +jintArray pythonSequenceAsIntegerArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); + jsize jlen = (jsize)min(len, JAVA_MAXINT); + jintArray array = (*env)->NewIntArray(env, jlen); + + jint i; + + for (i = 0; i < jlen; i++) { + PyObject *item = PySequence_GetItem(seq, i); + if (PyLong_Check(item)) { + jint value = PyLong_AsLong(item); + (*env)->SetIntArrayRegion(env, array, i, 1, &value); + } + else { + throwException(env, RUNTIME_EXCEPTION, "List item not an integer"); + return NULL; + } } - else { - int i; - for (i = 0; i < dims[d]; i++) { - copyDoubleArrayValues(env, array, (double*)((char*)data + strides[d] * i), offset, d+1, nd, strides, dims, ncont); + + return array; +} + +jlongArray pythonSequenceAsLongArray(JNIEnv *env, PyObject *seq) { + Py_ssize_t len = PySequence_Size(seq); + jsize jlen = (jsize)min(len, JAVA_MAXINT); + jlongArray array = (*env)->NewLongArray(env, jlen); + + jint i; + + for (i = 0; i < jlen; i++) { + PyObject *item = PySequence_GetItem(seq, i); + if (PyLong_Check(item)) { + jlong value = PyLong_AsLongLong(item); + (*env)->SetLongArrayRegion(env, array, i, 1, &value); + } + else { + throwException(env, RUNTIME_EXCEPTION, "List item not an integer"); + return NULL; } } + + return array; } jobject pythonArrayAsNDArray(JNIEnv *env, PyArrayObject *array) { @@ -303,33 +755,49 @@ jobject pythonArrayAsNDArray(JNIEnv *env, PyArrayObject *array) { } } -jintArray pythonListAsIntegerArray(JNIEnv *env, PyObject *list) { - Py_ssize_t len = PyList_Size(list); - jsize jlen = (jsize)min(len, JAVA_MAXINT); - jdoubleArray array = (*env)->NewIntArray(env, jlen); +jobject pythonDictionaryAsMap(JNIEnv *env, PyObject *dict) { + jclass hashmapClass = (*env)->FindClass(env, "java/util/HashMap"); + jmethodID constructor = (*env)->GetMethodID(env, hashmapClass, "", "(I)V"); + jmethodID putMethod = (*env)->GetMethodID(env, hashmapClass, "put", "(L" OBJECT_CLASS ";L" OBJECT_CLASS ";)L" OBJECT_CLASS ";"); - jint i; + Py_ssize_t size = PyDict_Size(dict); + jobject map = (*env)->NewObject(env, hashmapClass, constructor, (jint)size); - for (i = 0; i < jlen; i++) { - PyObject *item = PyList_GetItem(list, i); - if (PyLong_Check(item)) { - jint value = PyLong_AsLong(item); - (*env)->SetIntArrayRegion(env, array, i, 1, &value); - } - else { - throwException(env, RUNTIME_EXCEPTION, "List item not an integer"); - return NULL; - } + PyObject *key, *value; + Py_ssize_t pos = 0; + + while (PyDict_Next(dict, &pos, &key, &value)) { + jobject keyObject = pythonObjectAsObject(env, key); + jobject valueObject = pythonObjectAsObject(env, value); + (*env)->CallObjectMethod(env, map, putMethod, keyObject, valueObject); } - return array; + return map; } -JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonIntegerVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jint value) { +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonBooleanVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jboolean value) { PyObject *module = (PyObject*)contextID; PyObject *pythonName = getPythonString(env, variableName); - PyObject *val = PyLong_FromLong(value); + PyObject *val = getPythonBool(value); + + setPythonVariable(module, pythonName, val); +} + +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonBooleanArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jbooleanArray value) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + PyObject *val = getPythonBooleanList(env, value); + + setPythonVariable(module, pythonName, val); +} + +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonLongVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jlong value) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + PyObject *val = PyLong_FromLongLong(value); setPythonVariable(module, pythonName, val); } @@ -343,6 +811,15 @@ JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonInte setPythonVariable(module, pythonName, val); } +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonLongArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jlongArray value) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + PyObject *val = getPythonLongList(env, value); + + setPythonVariable(module, pythonName, val); +} + JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonDoubleVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jdouble value) { PyObject *module = (PyObject*)contextID; @@ -352,6 +829,15 @@ JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonDoub setPythonVariable(module, pythonName, val); } +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonFloatArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jfloatArray value) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + PyObject *val = getPythonFloatList(env, value); + + setPythonVariable(module, pythonName, val); +} + JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonDoubleArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jdoubleArray value) { PyObject *module = (PyObject*)contextID; @@ -382,7 +868,7 @@ JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonStri JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonNDArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jobject value) { PyObject *module = (PyObject*)contextID; - if (_import_array() < 0) { + if (!hasNumpy) { throwException(env, RUNTIME_EXCEPTION, "Importing numpy failed"); return; } @@ -395,6 +881,15 @@ JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonNDAr } } +JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonVariantVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jobject value, jobject binding) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + PyObject *val = getPythonObject(env, value, binding); + + setPythonVariable(module, pythonName, val); +} + JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_executePythonStatementImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring statement) { PyObject *module = (PyObject*)contextID; @@ -418,8 +913,6 @@ JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_executePython throwException(env, RUNTIME_EXCEPTION, message); } - // Py_XDECREF(globals); - (*env)->ReleaseStringUTFChars(env, statement, utfchars); return result != NULL ? 0 : 1; @@ -443,7 +936,10 @@ JNIEXPORT jstring JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonS return 0; } - return pythonStringAsJavaString(env, value); + { + jstring result = pythonStringAsJavaString(env, value); + return result; + } } JNIEXPORT jobjectArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonStringArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { @@ -457,15 +953,59 @@ JNIEXPORT jobjectArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPy return 0; } - if (!PyList_Check(value)) { - throwException(env, RUNTIME_EXCEPTION, "Python variable not a list"); + if (!PySequence_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a sequence"); + return 0; + } + + { + jobjectArray result = pythonSequenceAsStringArray(env, value); + return result; + } +} + +JNIEXPORT jboolean JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonBooleanVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + + PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName); + if (value == NULL) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not found"); + return 0; + } + + if (!PyBool_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a boolean"); return 0; } - return pythonStringListAsJavaArray(env, value); + return value == Py_True; } -JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonIntegerVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { +JNIEXPORT jbooleanArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonBooleanArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + + PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName); + if (value == NULL) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not found"); + return 0; + } + + if (!PySequence_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a sequence"); + return 0; + } + + { + jbooleanArray result = pythonSequenceAsBooleanArray(env, value); + return result; + } +} + +JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonLongVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { PyObject *module = (PyObject*)contextID; PyObject *pythonName = getPythonString(env, variableName); @@ -481,7 +1021,10 @@ JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonInte return 0; } - return PyLong_AsLong(value); + { + jlong result = PyLong_AsLongLong(value); + return result; + } } JNIEXPORT jintArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonIntegerArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { @@ -495,12 +1038,37 @@ JNIEXPORT jintArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPytho return NULL; } - if (!PyList_Check(value)) { - throwException(env, RUNTIME_EXCEPTION, "Python variable not a list"); + if (!PySequence_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a sequence"); return NULL; } - return pythonListAsIntegerArray(env, value); + { + jintArray result = pythonSequenceAsIntegerArray(env, value); + return result; + } +} + +JNIEXPORT jlongArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonLongArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + + PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName); + if (value == NULL) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not found"); + return NULL; + } + + if (!PySequence_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a sequence"); + return NULL; + } + + { + jlongArray result = pythonSequenceAsLongArray(env, value); + return result; + } } JNIEXPORT jdouble JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonDoubleVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { @@ -519,7 +1087,10 @@ JNIEXPORT jdouble JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonD return 0.0; } - return PyFloat_AsDouble(value); + { + jdouble result = PyFloat_AsDouble(value); + return result; + } } JNIEXPORT jdoubleArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonDoubleArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { @@ -533,18 +1104,21 @@ JNIEXPORT jdoubleArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPy return NULL; } - if (!PyList_Check(value)) { - throwException(env, RUNTIME_EXCEPTION, "Python variable not a list"); + if (!PySequence_Check(value)) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not a sequence"); return NULL; } - return pythonListAsDoubleArray(env, value); + { + jdoubleArray result = pythonSequenceAsDoubleArray(env, value); + return result; + } } JNIEXPORT jobject JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonNDArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { PyObject *module = (PyObject*)contextID; - if (_import_array() < 0) { + if (!hasNumpy) { throwException(env, RUNTIME_EXCEPTION, "Importing numpy failed"); return NULL; } @@ -568,8 +1142,88 @@ JNIEXPORT jobject JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonN return NULL; } - return pythonArrayAsNDArray(env, (PyArrayObject *)value); + { + jobject result = pythonArrayAsNDArray(env, (PyArrayObject *)value); + return result; + } + } +} + +JNIEXPORT jobject JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonVariantVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { + PyObject *module = (PyObject*)contextID; + + PyObject *pythonName = getPythonString(env, variableName); + + PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName); + if (value == NULL) { + throwException(env, RUNTIME_EXCEPTION, "Python variable not found"); + return NULL; } + + hasNumpy = _import_array() != -1; + + { + jobject result = pythonObjectAsObject(env, value); + return result; + } +} + +JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonVariableTypeImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) { + PyObject *module = (PyObject*)contextID; + PyObject *dict = PyModule_GetDict(module); + + PyObject *pythonName = getPythonString(env, variableName); + + if (!PyDict_Contains(dict, pythonName)) { + return 0; + } + + { + PyObject *value = PyDict_GetItem(dict, pythonName); + + jint result; + + if (PyBool_Check(value)) + result = 1; + else if (PyLong_Check(value)) + result = 2; + else if (PyFloat_Check(value)) + result = 3; + else if (PyUnicode_Check(value)) + result = 4; + else if (PyByteArray_Check(value)) + result = 5; + else if (PyDict_Check(value)) + result = 6; + else if (hasNumpy && PyArray_Check(value)) + result = 7; + else if (PySequence_Check(value)) + result = 8; + else + result = -1; + + return result; + } +} + +JNIEXPORT jobjectArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonVariableNamesImpl(JNIEnv *env, jobject thisObj, jlong contextID) { + PyObject *module = (PyObject*)contextID; + PyObject *dict = PyModule_GetDict(module); + + PyObject *keys = PyDict_Keys(dict); + Py_ssize_t size = PyList_Size(keys); + + jobjectArray result = (*env)->NewObjectArray(env, (jsize)size, (*env)->FindClass(env, STRING_CLASS), NULL); + + Py_ssize_t i; + for (i = 0; i < size; i++) { + jstring javaName = pythonStringAsJavaString(env, PyList_GetItem(keys, i)); + (*env)->SetObjectArrayElement(env, result, (jint)i, javaName); + } + + Py_XDECREF(keys); + + return result; } BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) @@ -580,12 +1234,10 @@ BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) case DLL_PROCESS_ATTACH: // attach to process // return FALSE to fail DLL load - Py_Initialize(); break; case DLL_PROCESS_DETACH: // detach from process - Py_Finalize(); break; case DLL_THREAD_ATTACH: diff --git a/org.simantics.pythonlink.win32.x86_64/src/sclpy.h b/org.simantics.pythonlink.win32.x86_64/src/sclpy.h index ec3b400..4a2c7de 100644 --- a/org.simantics.pythonlink.win32.x86_64/src/sclpy.h +++ b/org.simantics.pythonlink.win32.x86_64/src/sclpy.h @@ -1,6 +1,19 @@ #ifndef __MAIN_H__ #define __MAIN_H__ +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#ifdef _DEBUG + #undef _DEBUG + #include //header for system python import; add include paths + #include + #define _DEBUG 1 +#else + #include //header for system python import; add include paths + #include +#endif + +#include //java connection header + /* To use this exported function of dll, include this header * in your project. */ @@ -11,4 +24,92 @@ #define DLL_EXPORT __declspec(dllimport) #endif + +#define JAVA_MAXINT (0x7fffffff) + +#define RUNTIME_EXCEPTION "java/lang/RuntimeException" +#define ILLEGAL_ARGUMENT_EXCEPTION "java/lang/IllegalArgumentException" +#define OBJECT_CLASS "java/lang/Object" +#define STRING_CLASS "java/lang/String" +#define MAP_CLASS "java/util/Map" +#define SET_CLASS "java/util/Set" + +#define PACKAGE_PREFIX "org/simantics/pythonlink/" + +#define NDARRAY_CLASS (PACKAGE_PREFIX "NDArray") + +#define VARIANT_CLASS "org/simantics/databoard/binding/mutable/Variant" +#define BINDINGS_CLASS "org/simantics/databoard/Bindings" +#define BINDING_CLASS "org/simantics/databoard/binding/Binding" +#define DATATYPE_CLASS "org/simantics/databoard/type/Datatype" + +#define BOOLEANTYPE_CLASS "org/simantics/databoard/type/BooleanType" +#define BYTETYPE_CLASS "org/simantics/databoard/type/ByteType" +#define INTEGERTYPE_CLASS "org/simantics/databoard/type/IntegerType" +#define LONGTYPE_CLASS "org/simantics/databoard/type/LongType" +#define FLOATTYPE_CLASS "org/simantics/databoard/type/FloatType" +#define DOUBLETYPE_CLASS "org/simantics/databoard/type/DoubleType" +#define STRINGTYPE_CLASS "org/simantics/databoard/type/StringType" +#define RECORDTYPE_CLASS "org/simantics/databoard/type/RecordType" +#define ARRAYTYPE_CLASS "org/simantics/databoard/type/ArrayType" +#define MAPTYPE_CLASS "org/simantics/databoard/type/MapType" +#define OPTIONALTYPE_CLASS "org/simantics/databoard/type/OptionalType" +#define UNIONTYPE_CLASS "org/simantics/databoard/type/UnionType" +#define VARIANTTYPE_CLASS "org/simantics/databoard/type/VariantType" + +#define BOOLEANBINDING_CLASS "org/simantics/databoard/binding/BooleanBinding" +#define BYTEBINDING_CLASS "org/simantics/databoard/binding/ByteBinding" +#define INTEGERBINDING_CLASS "org/simantics/databoard/binding/IntegerBinding" +#define LONGBINDING_CLASS "org/simantics/databoard/binding/LongBinding" +#define FLOATBINDING_CLASS "org/simantics/databoard/binding/FloatBinding" +#define DOUBLEBINDING_CLASS "org/simantics/databoard/binding/DoubleBinding" +#define STRINGBINDING_CLASS "org/simantics/databoard/binding/StringBinding" +#define RECORDBINDING_CLASS "org/simantics/databoard/binding/RecordBinding" +#define ARRAYBINDING_CLASS "org/simantics/databoard/binding/ArrayBinding" +#define MAPBINDING_CLASS "org/simantics/databoard/binding/MapBinding" +#define OPTIONALBINDING_CLASS "org/simantics/databoard/binding/OptionalBinding" +#define UNIONBINDING_CLASS "org/simantics/databoard/binding/UnionBinding" +#define VARIANTBINDING_CLASS "org/simantics/databoard/binding/VariantBinding" + +#define COMPONENT_CLASS "org/simantics/databoard/type/Component" +#define TAGGEDOBJECT_CLASS "org/simantics/databoard/binding/mutable/TaggedObject" + +PyObject *getPythonBooleanList(JNIEnv *env, jbooleanArray value); +PyObject *getPythonByteArray(JNIEnv *env, jbyteArray value); +PyObject *getPythonIntegerList(JNIEnv *env, jintArray value); +PyObject *getPythonLongList(JNIEnv *env, jlongArray value); +PyObject *getPythonFloatList(JNIEnv *env, jfloatArray value); +PyObject *getPythonDoubleList(JNIEnv *env, jdoubleArray value); + +PyObject *getPythonObject(JNIEnv *env, jobject object, jobject binding); + +PyObject *getPythonBooleanObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonByteObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonIntegerObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonLongObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonFloatObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonDoubleObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonRecordObject(JNIEnv *env, jobjectArray object, jobject binding); +PyObject *getPythonArrayObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonMapObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonOptionalObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonUnionObject(JNIEnv *env, jobject object, jobject binding); +PyObject *getPythonVariantObject(JNIEnv *env, jobject object, jobject binding); + +void setPythonVariable(PyObject *module, PyObject *name, PyObject *value); + +jobject pythonBoolAsBooleanObject(JNIEnv *env, PyObject *value); +jobject pythonLongAsLongObject(JNIEnv *env, PyObject *value); +jobject pythonFloatAsDoubleObject(JNIEnv *env, PyObject *value); +jobject pythonByteArrayAsByteArray(JNIEnv *env, PyObject *value); +jstring pythonStringAsJavaString(JNIEnv *env, PyObject *string); +jobjectArray pythonSequenceAsObjectArray(JNIEnv *env, PyObject *seq); +jobjectArray pythonSequenceAsStringArray(JNIEnv *env, PyObject *list); +jintArray pythonSequenceAsIntegerArray(JNIEnv *env, PyObject *list); +jlongArray pythonSequenceAsLongArray(JNIEnv *env, PyObject *list); +jdoubleArray pythonSequenceAsDoubleArray(JNIEnv *env, PyObject *list); +jobject pythonDictionaryAsMap(JNIEnv *env, PyObject *dict); +jobject pythonArrayAsNDArray(JNIEnv *env, PyArrayObject *array); +jobject pythonObjectAsObject(JNIEnv *env, PyObject *value); + #endif // __MAIN_H__ diff --git a/org.simantics.pythonlink/META-INF/MANIFEST.MF b/org.simantics.pythonlink/META-INF/MANIFEST.MF index 14c621b..427f880 100644 --- a/org.simantics.pythonlink/META-INF/MANIFEST.MF +++ b/org.simantics.pythonlink/META-INF/MANIFEST.MF @@ -9,7 +9,14 @@ Require-Bundle: org.junit, org.simantics.scl.runtime, gnu.trove3;bundle-version="3.0.3", org.simantics.scl.compiler, - org.simantics.scl.osgi + org.simantics.scl.osgi, + org.simantics.db.layer0;bundle-version="1.1.0", + org.simantics.simulator.variable, + org.simantics, + org.simantics.application, + org.simantics.db.management, + org.simantics.scl.db;bundle-version="0.1.3", + org.simantics.modeling;bundle-version="1.1.1" Export-Package: org.simantics.pythonlink Bundle-Activator: org.simantics.pythonlink.Activator Bundle-ActivationPolicy: lazy diff --git a/org.simantics.pythonlink/runAllTests.launch b/org.simantics.pythonlink/runAllTests.launch index 8b34091..4444941 100644 --- a/org.simantics.pythonlink/runAllTests.launch +++ b/org.simantics.pythonlink/runAllTests.launch @@ -13,6 +13,7 @@ + @@ -33,12 +34,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.pythonlink/runScriptTests.launch b/org.simantics.pythonlink/runScriptTests.launch index b4c34eb..dadf334 100644 --- a/org.simantics.pythonlink/runScriptTests.launch +++ b/org.simantics.pythonlink/runScriptTests.launch @@ -35,12 +35,48 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/org.simantics.pythonlink/scl/Python.scl b/org.simantics.pythonlink/scl/Python.scl index b1ca052..5fc7831 100644 --- a/org.simantics.pythonlink/scl/Python.scl +++ b/org.simantics.pythonlink/scl/Python.scl @@ -1,5 +1,3 @@ -import "Vector" - effect Python "Simantics/Python/Python" "org.simantics.pythonlink.PythonContext" @@ -36,6 +34,27 @@ importJava "org.simantics.pythonlink.NDArray" where @JavaName equals ndarrayEquals :: NDArray -> NDArray -> Boolean +class NDArrayInput a where + ndarrayInput :: a -> ([Integer], [Double]) + +instance NDArrayInput [Double] where + ndarrayInput v = ([length v], v) + +instance (NDArrayInput [a]) => NDArrayInput [[a]] where + ndarrayInput v = let + inp = map ndarrayInput v + dims = map fst inp + vals = join $ map snd inp + in + if not (all (== (dims!0)) dims) then + fail "List of lists not of uniform dimension" + else + ([length v] + (dims!0), vals) + +ndarrayFromList :: NDArrayInput a => a -> NDArray +ndarrayFromList v = ndarrayN (vector dims) (vector vals) where + (dims, vals) = ndarrayInput v + instance Show NDArray where show = ndarrayToString @@ -50,22 +69,35 @@ importJava "org.simantics.pythonlink.PythonContext" where executePythonStatement :: String -> () + setPythonBooleanVariable :: String -> Boolean -> () setPythonIntegerVariable :: String -> Integer -> () - setPythonIntegerArrayVariable :: String -> Vector Integer -> () + setPythonLongVariable :: String -> Long -> () setPythonDoubleVariable :: String -> Double -> () - setPythonDoubleArrayVariable :: String -> Vector Double -> () setPythonStringVariable :: String -> String -> () + + setPythonBooleanArrayVariable :: String -> Vector Boolean -> () + setPythonIntegerArrayVariable :: String -> Vector Integer -> () + setPythonLongArrayVariable :: String -> Vector Long -> () + setPythonDoubleArrayVariable :: String -> Vector Double -> () setPythonStringArrayVariable :: String -> Vector String -> () setPythonNDArrayVariable :: String -> NDArray -> () + getPythonBooleanVariable :: String -> Boolean getPythonIntegerVariable :: String -> Integer - getPythonIntegerArrayVariable :: String -> Vector Integer + getPythonLongVariable :: String -> Long getPythonDoubleVariable :: String -> Double - getPythonDoubleArrayVariable :: String -> Vector Double getPythonStringVariable :: String -> String + + getPythonBooleanArrayVariable :: String -> Vector Boolean + getPythonIntegerArrayVariable :: String -> Vector Integer + getPythonLongArrayVariable :: String -> Vector Long + getPythonDoubleArrayVariable :: String -> Vector Double getPythonStringArrayVariable :: String -> Vector String getPythonNDArrayVariable :: String -> NDArray + setPythonVariantVariable :: String -> Variant -> () + getPythonVariantVariable :: String -> Variant + importJava "org.simantics.pythonlink.Python" where openPythonContext :: PythonContext diff --git a/org.simantics.pythonlink/scl/PythonVariable.scl b/org.simantics.pythonlink/scl/PythonVariable.scl new file mode 100644 index 0000000..55db31c --- /dev/null +++ b/org.simantics.pythonlink/scl/PythonVariable.scl @@ -0,0 +1,5 @@ +include "Python" +include "Simantics/Variables" + +importJava "org.simantics.pythonlink.Python" where + getPythonContextVariable :: PythonContext -> Variable diff --git a/org.simantics.pythonlink/src/org/simantics/pythonlink/Python.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/Python.java index 07921fd..9d00c44 100644 --- a/org.simantics.pythonlink/src/org/simantics/pythonlink/Python.java +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/Python.java @@ -1,8 +1,20 @@ package org.simantics.pythonlink; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.simantics.db.layer0.variable.NodeSupport; +import org.simantics.db.layer0.variable.StandardGraphChildVariable; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.layer0.variable.VariableNode; +import org.simantics.pythonlink.PythonContext.Listener; +import org.simantics.pythonlink.variable.PythonNode; +import org.simantics.pythonlink.variable.PythonNodeManager; import org.simantics.scl.runtime.SCLContext; import org.simantics.scl.runtime.function.Function; import org.simantics.scl.runtime.tuple.Tuple0; +import org.simantics.simulator.variable.NodeManager; +import org.simantics.simulator.variable.exceptions.NodeManagerException; public class Python { private static final String PYTHON_CONTEXT = "Simantics/Python/Python"; @@ -11,6 +23,50 @@ public class Python { return new PythonContext(); } + public static Variable getPythonContextVariable(PythonContext context) { + NodeManager nodeManager = new PythonNodeManager(context); + PythonNode root; + try { + root = nodeManager.getNode("/"); + } catch (NodeManagerException e) { + // Should not happen + throw new RuntimeException("Getting root Python node failed"); + } + + final NodeSupport support = new NodeSupport(nodeManager, 0, TimeUnit.NANOSECONDS); + + context.addListener(new Listener() { + @Override + public void updated(String variableName) { + try { + PythonNode root = nodeManager.getNode("/"); + if (variableName != null) { + PythonNode node = nodeManager.getNode(variableName); + if (node != null) support.valueCache.removeListening(node); + support.structureCache.removeListening(root); + } + else { + List props = nodeManager.getProperties(root); + for (PythonNode p : props) + support.valueCache.removeListening(p); + support.structureCache.removeListening(root); + } + + support.valueCache.clearExpired(); + support.structureCache.clearExpired(); + } catch (NodeManagerException e) { + e.printStackTrace(); + } + } + + @Override + public void closed() { + } + }); + + return new StandardGraphChildVariable(null, new VariableNode(support, root), null); + } + @SuppressWarnings( { "unchecked", "rawtypes" } ) public static Object runPythonF(Function f) { SCLContext sclContext = SCLContext.getCurrent(); diff --git a/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java index 5341cc2..6c96dd3 100644 --- a/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java @@ -1,21 +1,73 @@ package org.simantics.pythonlink; import java.io.Closeable; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Pattern; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.binding.Binding; +import org.simantics.databoard.binding.error.BindingException; +import org.simantics.databoard.binding.mutable.Variant; public class PythonContext implements Closeable { private long contextID; + public interface Listener { + void updated(String variableName); + void closed(); + } + + static ExecutorService pythonExecutor = Executors.newSingleThreadExecutor(); + + Set listeners = new HashSet<>(); + + public enum VariableType { + NO_VARIABLE, + BOOLEAN, + LONG, + FLOAT, + STRING, + BYTEARRAY, + DICTIONARY, + NDARRAY, + SEQUENCE, + UNKNOWN + } + + static Pattern namePattern = Pattern.compile("([a-zA-Z_][a-zA-Z_0-9]*)"); + PythonContext() { contextID = createContextImpl(); } + public void addListener(Listener listener) { + listeners.add(listener); + } + + public void removeListener(Listener listener) { + listeners.remove(listener); + } + @Override public void close() { long id = contextID; contextID = 0; if (id != 0) deleteContextImpl(id); + + for (Listener l : listeners) { + l.closed(); + } } + public boolean isOpen() { + return contextID != 0; + } + @Override protected void finalize() throws Throwable { super.finalize(); @@ -23,82 +75,220 @@ public class PythonContext implements Closeable { } public void executePythonStatement(String statement) { - executePythonStatementImpl( contextID, statement ); + execute(() -> executePythonStatementImpl( contextID, statement )); + for (Listener l : listeners) { l.updated(null); } } // Setters + public void setPythonBooleanVariable(String variableName, boolean value) { + checkValidName(variableName); + execute(() -> setPythonBooleanVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } + } + public void setPythonIntegerVariable(String variableName, int value) { - setPythonIntegerVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonLongVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } + } + public void setPythonLongVariable(String variableName, long value) { + checkValidName(variableName); + execute(() -> setPythonLongVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } public void setPythonDoubleVariable(String variableName, double value) { - setPythonDoubleVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonDoubleVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } public void setPythonStringVariable(String variableName, String value) { - setPythonStringVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonStringVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } + public void setPythonBooleanArrayVariable(String variableName, boolean[] value) { + checkValidName(variableName); + execute(() -> setPythonBooleanArrayVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } + } public void setPythonIntegerArrayVariable(String variableName, int[] value) { - setPythonIntegerArrayVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonIntegerArrayVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } + } + public void setPythonLongArrayVariable(String variableName, long[] value) { + checkValidName(variableName); + execute(() -> setPythonLongArrayVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } public void setPythonDoubleArrayVariable(String variableName, double[] value) { - setPythonDoubleArrayVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonDoubleArrayVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } public void setPythonStringArrayVariable(String variableName, String[] value) { - setPythonStringArrayVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonStringArrayVariableImpl(contextID, variableName, value)); + for (Listener l : listeners) { l.updated(variableName); } } // Getters + public boolean getPythonBooleanVariable(String variableName) { + checkValidName(variableName); + return getPythonBooleanVariableImpl(contextID, variableName); + } public int getPythonIntegerVariable(String variableName) { - return getPythonIntegerVariableImpl(contextID, variableName); + checkValidName(variableName); + long value = execute(() -> getPythonLongVariableImpl(contextID, variableName)); + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) + throw new RuntimeException("Python value not in integer range"); + return (int) value; + } + public long getPythonLongVariable(String variableName) { + checkValidName(variableName); + return execute(() -> getPythonLongVariableImpl(contextID, variableName)); } public double getPythonDoubleVariable(String variableName) { - return getPythonDoubleVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonDoubleVariableImpl(contextID, variableName)); } public String getPythonStringVariable(String variableName) { - return getPythonStringVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonStringVariableImpl(contextID, variableName)); } + public boolean[] getPythonBooleanArrayVariable(String variableName) { + checkValidName(variableName); + return execute(() -> getPythonBooleanArrayVariableImpl(contextID, variableName)); + } public int[] getPythonIntegerArrayVariable(String variableName) { - return getPythonIntegerArrayVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonIntegerArrayVariableImpl(contextID, variableName)); + } + public long[] getPythonLongArrayVariable(String variableName) { + checkValidName(variableName); + return execute(() -> getPythonLongArrayVariableImpl(contextID, variableName)); } public double[] getPythonDoubleArrayVariable(String variableName) { - return getPythonDoubleArrayVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonDoubleArrayVariableImpl(contextID, variableName)); } public String[] getPythonStringArrayVariable(String variableName) { - return getPythonStringArrayVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonStringArrayVariableImpl(contextID, variableName)); } public void setPythonNDArrayVariable(String variableName, NDArray value) { - setPythonNDArrayVariableImpl(contextID, variableName, value); + checkValidName(variableName); + execute(() -> setPythonNDArrayVariableImpl(contextID, variableName, value)); } public NDArray getPythonNDArrayVariable(String variableName) { - return getPythonNDArrayVariableImpl(contextID, variableName); + checkValidName(variableName); + return execute(() -> getPythonNDArrayVariableImpl(contextID, variableName)); } + public Object getPythonVariantVariable(String variableName, Binding binding) { + checkValidName(variableName); + Object result = execute(() -> getPythonVariantVariableImpl(contextID, variableName)); + try { + return Bindings.OBJECT.getContent(result, binding); + } catch (BindingException e) { + throw new RuntimeException(e); + } + } + + public Variant getPythonVariantVariable(String variableName) { + checkValidName(variableName); + return Variant.ofInstance(execute(() -> getPythonVariantVariableImpl(contextID, variableName))); + } + + public void setPythonVariantVariable(String variableName, Variant value) { + setPythonVariantVariable(variableName, value.getValue(), value.getBinding()); + } + + public void setPythonVariantVariable(String variableName, Object value, Binding binding) { + checkValidName(variableName); + if (!binding.isInstance(value)) throw new IllegalArgumentException("Invalid object binding"); + + execute(() -> setPythonVariantVariableImpl(contextID, variableName, value, binding)); + + for (Listener l : listeners) { l.updated(variableName); } + } + + public VariableType getPythonVariableType(String variableName) { + checkValidName(variableName); + int code = execute(() -> getPythonVariableTypeImpl(contextID, variableName)); + + VariableType[] values = VariableType.values(); + if (code < 0 || code >= values.length) + return VariableType.UNKNOWN; + + return values[code]; + } + + public String[] getPythonVariableNames() { + return execute(() -> getPythonVariableNamesImpl(contextID)); + } + + private static void checkValidName(String variableName) { + if (!namePattern.matcher(variableName).matches()) + throw new IllegalArgumentException("Invalid Python variable name " + variableName); + } + + static void execute(Runnable job) { + try { + pythonExecutor.submit(job).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + static V execute(Callable job) { + try { + return pythonExecutor.submit(job).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + // Native function declarations private static native long createContextImpl(); private static native void deleteContextImpl(long contextID); private static native int executePythonStatementImpl(long contextID, String statement); - private static native void setPythonIntegerVariableImpl(long contextID, String variableName, int value); + private static native void setPythonBooleanVariableImpl(long contextID, String variableName, boolean value); + private static native void setPythonLongVariableImpl(long contextID, String variableName, long value); private static native void setPythonDoubleVariableImpl(long contextID, String variableName, double value); private static native void setPythonStringVariableImpl(long contextID, String variableName, String value); + private static native void setPythonBooleanArrayVariableImpl(long contextID, String variableName, boolean[] value); private static native void setPythonIntegerArrayVariableImpl(long contextID, String variableName, int[] value); + private static native void setPythonLongArrayVariableImpl(long contextID, String variableName, long[] value); private static native void setPythonDoubleArrayVariableImpl(long contextID, String variableName, double[] value); private static native void setPythonStringArrayVariableImpl(long contextID, String variableName, String[] value); - private static native int getPythonIntegerVariableImpl(long contextID, String variableName); + private static native boolean getPythonBooleanVariableImpl(long contextID, String variableName); + private static native long getPythonLongVariableImpl(long contextID, String variableName); private static native double getPythonDoubleVariableImpl(long contextID, String variableName); private static native String getPythonStringVariableImpl(long contextID, String variableName); + private static native boolean[] getPythonBooleanArrayVariableImpl(long contextID, String variableName); + private static native long[] getPythonLongArrayVariableImpl(long contextID, String variableName); private static native int[] getPythonIntegerArrayVariableImpl(long contextID, String variableName); private static native double[] getPythonDoubleArrayVariableImpl(long contextID, String variableName); private static native String[] getPythonStringArrayVariableImpl(long contextID, String variableName); private static native void setPythonNDArrayVariableImpl(long contextID, String variableName, NDArray value); private static native NDArray getPythonNDArrayVariableImpl(long contextID, String variableName); + + private static native void setPythonVariantVariableImpl(long contextID, String variableName, Object value, Binding binding); + private static native Object getPythonVariantVariableImpl(long contextID, String variableName); + + private static native int getPythonVariableTypeImpl(long contextID, String variableName); + + private static native String[] getPythonVariableNamesImpl(long contextID); } diff --git a/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNode.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNode.java new file mode 100644 index 0000000..c7e9df5 --- /dev/null +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNode.java @@ -0,0 +1,14 @@ +package org.simantics.pythonlink.variable; + +public class PythonNode { + public PythonNode(String variableName) { + super(); + this.variableName = variableName; + } + + private String variableName; + + public String getName() { + return variableName; + } +} diff --git a/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNodeManager.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNodeManager.java new file mode 100644 index 0000000..b88ebfa --- /dev/null +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/variable/PythonNodeManager.java @@ -0,0 +1,281 @@ +package org.simantics.pythonlink.variable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.Datatypes; +import org.simantics.databoard.adapter.AdaptException; +import org.simantics.databoard.binding.Binding; +import org.simantics.databoard.binding.error.BindingException; +import org.simantics.databoard.binding.mutable.Variant; +import org.simantics.databoard.type.Datatype; +import org.simantics.databoard.type.MapType; +import org.simantics.databoard.type.RecordType; +import org.simantics.pythonlink.NDArray; +import org.simantics.pythonlink.PythonContext; +import org.simantics.pythonlink.PythonContext.Listener; +import org.simantics.pythonlink.PythonContext.VariableType; +import org.simantics.simulator.variable.Realm; +import org.simantics.simulator.variable.exceptions.NodeManagerException; +import org.simantics.simulator.variable.impl.AbstractNodeManager; + +public class PythonNodeManager extends AbstractNodeManager { + PythonContext context; + + static Realm realm = new Realm() { + public void asyncExec(Runnable runnable) { + runnable.run(); + }; + + public void syncExec(Runnable runnable) { + runnable.run(); + }; + }; + + Map> listeners = new HashMap<>(); + Map nodes = new HashMap<>(); + + PythonNode root = new PythonNode("/"); + + static Pattern namePattern = Pattern.compile("/?([a-zA-Z_][a-zA-Z_0-9]*)"); + + static MapType dictionaryType = new MapType(Datatypes.VARIANT, Datatypes.VARIANT); + static RecordType ndarrayType = (RecordType)Bindings.getBindingUnchecked(NDArray.class).type(); + + public PythonNodeManager(PythonContext context) { + super(); + this.context = context; + + context.addListener(new Listener() { + @Override + public void updated(String variableName) { + if (variableName != null) { + Set lis = listeners.get(variableName); + if (lis != null) { + for (Runnable r : lis) + r.run(); + } + lis = listeners.get("/"); + if (lis != null) { + for (Runnable r : lis) + r.run(); + } + } + else { + Set allRunnables = new HashSet<>(); + for (Collection s : listeners.values()) + allRunnables.addAll(s); + for (Runnable r : allRunnables) + r.run(); + } + } + + @Override + public void closed() { + nodes.clear(); + Set allRunnables = new HashSet<>(); + for (Collection s : listeners.values()) + allRunnables.addAll(s); + for (Runnable r : allRunnables) + r.run(); + } + }); + } + + @Override + public Realm getRealm() { + return realm; + } + + @Override + public String getName(PythonNode node) { + return node.getName(); + } + + @Override + public void addNodeListener(PythonNode node, Runnable listener) { + synchronized(listeners) { + if (!listeners.containsKey(node.getName())) listeners.put(node.getName(), new HashSet<>()); + listeners.get(node.getName()).add(listener); + } + } + + @Override + public void removeNodeListener(PythonNode node, Runnable listener) { + synchronized(listeners) { + if (!listeners.containsKey(node.getName())) + listeners.get(node.getName()).remove(listener); + } + } + + @Override + public PythonNode getNode(String path) throws NodeManagerException { + if (!context.isOpen()) + return null; + + if ("/".equals(path)) + return root; + + Matcher match = namePattern.matcher(path); + if (!match.matches()) + return null; + + String name = match.group(1); + + if (nodes.containsKey(name)) + return nodes.get(name); + + VariableType type = context.getPythonVariableType(name); + if (type == VariableType.NO_VARIABLE) + return null; + + PythonNode node = new PythonNode(name); + nodes.put(path, node); + + return node; + } + + @Override + public PythonNode getChild(PythonNode node, String name) throws NodeManagerException { + return null; + } + + @Override + public PythonNode getProperty(PythonNode node, String name) throws NodeManagerException { + if (node != root || !context.isOpen()) return null; + + return getNode(name); + } + + @Override + public List getChildNames(PythonNode node) throws NodeManagerException { + return Collections.emptyList(); + } + + @Override + public List getChildren(PythonNode node) throws NodeManagerException { + return Collections.emptyList(); + } + + @Override + public List getPropertyNames(PythonNode node) throws NodeManagerException { + if (node != root || !context.isOpen()) return Collections.emptyList(); + return Arrays.asList(context.getPythonVariableNames()); + } + + @Override + public List getProperties(PythonNode node) throws NodeManagerException { + if (node != root || !context.isOpen()) return Collections.emptyList(); + + String[] names = context.getPythonVariableNames(); + List result = new ArrayList<>(names.length); + + for (String name : names) { + result.add(getNode(name)); + } + + return result; + } + + @Override + public Datatype getDatatype(PythonNode node) throws NodeManagerException { + String name = node.getName(); + VariableType type; + try { + type = context.getPythonVariableType(name); + } catch (RuntimeException e) { + throw new NodeManagerException("Failed to get type of variable " + name, e); + } + + switch (type) { + case NO_VARIABLE: return Datatypes.VOID; + case BOOLEAN: return Datatypes.BOOLEAN; + case LONG: return Datatypes.LONG; + case FLOAT: return Datatypes.DOUBLE; + case STRING: return Datatypes.STRING; + case BYTEARRAY: return Datatypes.BYTE_ARRAY; + case DICTIONARY: return dictionaryType; + case NDARRAY: return ndarrayType; + case SEQUENCE: return Datatypes.VARIANT_ARRAY; + case UNKNOWN: return Datatypes.VOID; + default: throw new RuntimeException("Unknown python variable type"); + } + } + + @Override + public Variant getValue(PythonNode node, String propertyName) throws NodeManagerException { + if (node == root && context.isOpen()) + return context.getPythonVariantVariable(propertyName); + + throw new NodeManagerException("No value available for " + node.getName() + "#" + propertyName); + } + + @Override + public Object getValue(PythonNode node, String propertyName, Binding binding) + throws NodeManagerException, BindingException { + Variant value = getValue(node, propertyName); + + try { + return value.getValue(binding); + } catch (AdaptException e) { + throw new BindingException(e); + } + } + + @Override + public Variant getValue(PythonNode node) throws NodeManagerException { + if (node == root || !context.isOpen()) + throw new NodeManagerException("No value available for " + node.getName()); + + String name = node.getName(); + + try { + return context.getPythonVariantVariable(name); + } catch (RuntimeException e) { + throw new NodeManagerException("Failed to get value of variable " + name, e); + } + } + + @Override + public Object getValue(PythonNode node, Binding binding) throws NodeManagerException, BindingException { + Variant value = getValue(node); + + try { + return value.getValue(binding); + } catch (AdaptException e) { + throw new BindingException(e); + } + } + + @Override + public void setValue(PythonNode node, String propertyName, Object value, Binding binding) + throws NodeManagerException, BindingException { + if (node != root || !context.isOpen()) throw new NodeManagerException("No property " + node.getName() + "#" + propertyName); + + context.setPythonVariantVariable(propertyName, value, binding); + } + + @Override + public void setValue(PythonNode node, Object value, Binding binding) throws NodeManagerException, BindingException { + if (node == root || !context.isOpen()) throw new NodeManagerException("No property " + node.getName()); + String name = node.getName(); + + context.setPythonVariantVariable(name, value, binding); + } + + @Override + public Set getClassifications(PythonNode node) throws NodeManagerException { + return Collections.emptySet(); + } + +} diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/AllTests.java b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/AllTests.java index 3d9bbb2..8778150 100644 --- a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/AllTests.java +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/AllTests.java @@ -5,7 +5,7 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith( Suite.class ) -@SuiteClasses( { ScriptTests.class, TestPythonlink.class } ) +@SuiteClasses( { ScriptTests.class, TestPythonlink.class, TestPythonVariable.class } ) public class AllTests { } diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/ScriptTests.java b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/ScriptTests.java index 6a8bdae..22f81fe 100644 --- a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/ScriptTests.java +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/ScriptTests.java @@ -1,12 +1,37 @@ package org.simantics.pythonlink.test; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; +import org.simantics.PlatformException; +import org.simantics.Simantics; +import org.simantics.application.arguments.Arguments; +import org.simantics.pythonlink.PythonContext; public class ScriptTests extends ScriptTestBase { + static IProgressMonitor progress = new NullProgressMonitor(); + PythonContext python; + + @BeforeClass + public static void SetUpClass() throws PlatformException { + Simantics.startUpHeadless(Arguments.parse(new String[] {}), progress); + } + + @AfterClass + public static void TearDownClass() throws PlatformException { + Simantics.shutdown(progress); + } + public ScriptTests() { super("scripts"); } @Test public void Python() throws Exception { test(); } + @Test public void Matplotlib() throws Exception { test(); } + @Test public void NDArray() throws Exception { test(); } + @Test public void Variant() throws Exception { test(); } + @Test public void Variable() throws Exception { test(); } } diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/TestPythonVariable.java b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/TestPythonVariable.java new file mode 100644 index 0000000..56ff8e8 --- /dev/null +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/TestPythonVariable.java @@ -0,0 +1,139 @@ +package org.simantics.pythonlink.test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.simantics.PlatformException; +import org.simantics.Simantics; +import org.simantics.application.arguments.Arguments; +import org.simantics.databoard.Bindings; +import org.simantics.databoard.Datatypes; +import org.simantics.databoard.type.MapType; +import org.simantics.db.ReadGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.request.Read; +import org.simantics.pythonlink.Python; +import org.simantics.pythonlink.PythonContext; + +public class TestPythonVariable { + static IProgressMonitor progress = new NullProgressMonitor(); + + PythonContext python; + + @BeforeClass + public static void SetUpClass() throws PlatformException { + Simantics.startUpHeadless(Arguments.parse(new String[] {}), progress); + } + + @AfterClass + public static void TearDownClass() throws PlatformException { + Simantics.shutdown(progress); + } + + @Before + public void setUp() throws Exception { + python = Python.openPythonContext(); + } + + @After + public void tearDown() throws Exception { + python.close(); + } + + @Test + public void test() throws DatabaseException { + Simantics.getSession().syncRequest(new Read() { + @Override + public Void perform(ReadGraph graph) throws DatabaseException { + python.setPythonDoubleArrayVariable("foo", new double[] { 1, 2, 3 }); + + Variable var = Python.getPythonContextVariable(python); + + Object value = var.getPossiblePropertyValue(graph, "foo"); + + assertNotNull(value); + assertTrue(value instanceof Object[]); + assertArrayEquals(new Object[] {1.0, 2.0, 3.0}, (Object[])value); + + return null; + } + }); + } + + @Test + public void test2() throws DatabaseException { + Simantics.getSession().syncRequest(new Read() { + @Override + public Void perform(ReadGraph graph) throws DatabaseException { + python.setPythonDoubleArrayVariable("foo", new double[] { 1, 2, 3 }); + + Variable var = Python.getPythonContextVariable(python); + + Object value = var.getPossiblePropertyValue(graph, "foo", Bindings.DOUBLE_ARRAY); + + assertNotNull(value); + assertTrue(value instanceof double[]); + assertArrayEquals(new double[] {1.0, 2.0, 3.0}, (double[])value, 0.0); + + return null; + } + }); + } + + @Test + public void test3() throws DatabaseException { + Simantics.getSession().syncRequest(new Read() { + @Override + public Void perform(ReadGraph graph) throws DatabaseException { + python.executePythonStatement("foo = (1, 2, 3)"); + + Variable var = Python.getPythonContextVariable(python); + Variable foo = var.browse(graph, "#foo"); + + Object value = foo.getPossibleValue(graph, Bindings.LONG_ARRAY); + + assertNotNull(value); + assertTrue(value instanceof long[]); + assertArrayEquals(new long[] {1,2,3}, (long[])value); + + return null; + } + }); + } + + @Test + public void test4() throws DatabaseException { + Simantics.getSession().syncRequest(new Read() { + @Override + public Void perform(ReadGraph graph) throws DatabaseException { + python.executePythonStatement("foo = {1:'foo', 2:'bar'}"); + + Variable var = Python.getPythonContextVariable(python); + Variable foo = var.getPossibleProperty(graph, "foo"); + + Object value = foo.getPossibleValue(graph, Bindings.getBinding(new MapType(Datatypes.LONG, Datatypes.STRING))); + + assertNotNull(value); + assertTrue(value instanceof Map); + assertEquals(2, ((Map)value).size()); + assertEquals("foo", ((Map)value).get(1L)); + assertEquals("bar", ((Map)value).get(2L)); + + return null; + } + }); + } + +} diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Matplotlib.sts b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Matplotlib.sts new file mode 100644 index 0000000..ecff4df --- /dev/null +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Matplotlib.sts @@ -0,0 +1,28 @@ +> import "Python" +> +> code = """ +> import matplotlib +> import matplotlib.pyplot as plt +> import numpy as np +> import io +> +> x, y = np.random.randn(2, 100) +> fig = plt.figure() +> ax1 = fig.add_subplot(211) +> ax1.xcorr(x, y, usevlines=True, maxlags=50, normed=True, lw=2) +> ax1.grid(True) +> ax1.axhline(0, color='black', lw=2) +> +> ax2 = fig.add_subplot(212, sharex=ax1) +> ax2.acorr(x, usevlines=True, normed=True, maxlags=50, lw=2) +> ax2.grid(True) +> ax2.axhline(0, color='black', lw=2) +> +> imgdata = io.StringIO() +> fig.savefig(imgdata, format='svg') +> svg_data = imgdata.getvalue()[0:5] +> """ +> runPython do +> executePythonStatement code +> getPythonStringVariable "svg_data" +" import "Python" +> ndarray (vector [1, 2, 3]) +ndarray(3) [1.0, 2.0, 3.0] +> ndarrayM 2 2 (vector [1, 2, 3, 4]) +ndarray(2x2) [[1.0, 2.0], [3.0, 4.0]] +> a = ndarrayN (vector [2, 2, 3]) (vector [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) +> a +ndarray(2x2x3) [[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], [[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]] +> ndarrayDims a +vector [2, 2, 3] +> ndarrayValues a +vector [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0] +> ndarrayElement a 5 +6.0 +> ndarrayElementN a (vector [0, 1, 2]) +6.0 +> b = ndarrayFromList ([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]] :: [[[Double]]]) +> b +ndarray(2x2x3) [[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], [[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]] +> runPython do +> setPythonNDArrayVariable "foo" (ndarrayM 2 3 (vector [1, 2, 3, 4, 5, 6])) +> executePythonStatement "bar = foo.cumsum(1)" +> getPythonNDArrayVariable "bar" +ndarray(2x3) [[1.0, 3.0, 6.0], [4.0, 9.0, 15.0]] diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Python.sts b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Python.sts index 50c0423..83b2212 100644 --- a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Python.sts +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Python.sts @@ -5,6 +5,9 @@ > getPythonStringArrayVariable "d" vector ["__builtins__", "__doc__", "__loader__", "__name__", "__package__", "__spec__", "foo"] > runPython do +> getPythonStringVariable "__name__" +"SCL_2" +> runPython do > setPythonDoubleVariable "foo" 1 > executePythonStatement "foo = foo + 1" > getPythonDoubleVariable "foo" @@ -25,28 +28,21 @@ vector [1.0, 2.0, 3.0, 4.0] > executePythonStatement "foo.append(sum(foo))" > getPythonIntegerArrayVariable "foo" vector [1, 2, 3, 4, 10] -> ndarray (vector [1, 2, 3]) -ndarray(3) [1.0, 2.0, 3.0] -> ndarrayM 2 2 (vector [1, 2, 3, 4]) -ndarray(2x2) [[1.0, 2.0], [3.0, 4.0]] -> a = ndarrayN (vector [2, 2, 3]) (vector [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) -> a -ndarray(2x2x3) [[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], [[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]] -> ndarrayDims a -vector [2, 2, 3] -> ndarrayValues a -vector [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0] -> ndarrayElement a 5 -6.0 -> ndarrayElementN a (vector [0, 1, 2]) -6.0 -> runPython do -> setPythonNDArrayVariable "foo" (ndarrayM 2 3 (vector [1, 2, 3, 4, 5, 6])) -> executePythonStatement "bar = foo.cumsum(1)" -> getPythonNDArrayVariable "bar" -ndarray(2x3) [[1.0, 3.0, 6.0], [4.0, 9.0, 15.0]] +> runPython do +> setPythonBooleanVariable "foo" False +> executePythonStatement "foo = not(foo)" +> getPythonBooleanVariable "foo" +True > runPython do > setPythonVariable "foo" ([1.0, 2.0, 3.0, 4.0] :: [Double]) > executePythonStatement "foo.append(sum(foo))" > getPythonVariable "foo" :: [Double] [1.0, 2.0, 3.0, 4.0, 10.0] +> py = openPythonContext +> runWithPythonContext py $ setPythonIntegerVariable "foo" 4 +> runWithPythonContext py $ setPythonDoubleArrayVariable "bar" (vector [1.0, 2.0, 3.0]) +> runWithPythonContext py $ executePythonStatement "bar.append(float(foo))" +> bar = runWithPythonContext py $ getPythonDoubleArrayVariable "bar" +> closePythonContext py +> bar +vector [1.0, 2.0, 3.0, 4.0] diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variable.sts b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variable.sts new file mode 100644 index 0000000..a735dde --- /dev/null +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variable.sts @@ -0,0 +1,35 @@ +> import "PythonVariable" +> import "Simantics/DB" +> import "String" +> +> py = openPythonContext +> runWithPythonContext py do +> setPythonIntegerVariable "foo" 4 +> setPythonDoubleArrayVariable "bar" (vector [1.0, 2.0, 3.0]) +> +> var = getPythonContextVariable py +> sort $ map nameOf $ properties var +["__builtins__", "__doc__", "__loader__", "__name__", "__package__", "__spec__", "bar", "foo"] +> foo = property var "foo" +> bar = property var "bar" +> datatype foo +Long +> datatype bar +Variant[] +> value bar :: Variant +[1.0 : Double, 2.0 : Double, 3.0 : Double] : Variant[] +> value bar :: [Double] +[1.0, 2.0, 3.0] +> possibleChild var "foo" +Nothing +> possibleProperty var "dummy" +Nothing +> runWithPythonContext py do +> executePythonStatement "baz = (1.0, 'string_value', [2.0, 3.0])" +> sort $ map nameOf $ properties var +["__builtins__", "__doc__", "__loader__", "__name__", "__package__", "__spec__", "bar", "baz", "foo"] +> v = propertyValue var "baz" :: Variant +> v +[1.0 : Double, "string_value" : String, [2.0 : Double, 3.0 : Double] : Variant[]] : Variant[] +> variantElement v 1 :: String +"string_value" diff --git a/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variant.sts b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variant.sts new file mode 100644 index 0000000..745b3c3 --- /dev/null +++ b/org.simantics.pythonlink/test/org/simantics/pythonlink/test/scripts/Variant.sts @@ -0,0 +1,22 @@ +> import "Python" +> a = variant ([1.0, 2.0, 3.0]) +> b = runPython do +> setPythonVariantVariable "foo" a +> executePythonStatement "foo.append(4.0)" +> getPythonVariantVariable "foo" +> b +[1.0 : Double, 2.0 : Double, 3.0 : Double, 4.0 : Double] : Variant[] +> variantValue b :: [Double] +[1.0, 2.0, 3.0, 4.0] +> import "MMap" as MMap +> runPython do +> foo = MMap.create () :: MMap.T String Dynamic +> MMap.put foo "c1" (toDynamic (vector [True, True, False, True])) +> MMap.put foo "c2" (toDynamic ["foo", "bar", "baz"]) +> MMap.put foo "c3" (toDynamic 25.0) +> setPythonVariantVariable "foo" (variant foo) +> executePythonStatement "c1 = foo['c1']" +> executePythonStatement "c2 = foo['c2']" +> executePythonStatement "c3 = foo['c3']" +> (getPythonBooleanArrayVariable "c1", getPythonStringArrayVariable "c2", getPythonDoubleVariable "c3") +(vector [True, True, False, True], vector ["foo", "bar", "baz"], 25.0)