]> gerrit.simantics Code Review - simantics/python.git/blobdiff - org.simantics.pythonlink.win32.x86_64/src/sclpy.c
Several updates to Pyhthon-link behaviour.
[simantics/python.git] / org.simantics.pythonlink.win32.x86_64 / src / sclpy.c
index 1cc36d8e98ff4552c66eb9113f72570692375026..b2308bcdd3b4af57192c0fbd5a3dfa1ad6801e9f 100644 (file)
@@ -1,20 +1,21 @@
-///////////////////////////////////////////////////////
-//                                                   //
-//   VTT Technical Research Centre of Finland LTD    //
-//   For internal use only. Do not redistribute.     //
-//                                                   //
-//   Authors:                                        //
-//       Antton Tapani    ext-antton.tapani@vtt.fi   //
-//                                                   //
-//   Last modified by Antton Tapani    9.2016        //
-//                                                   //
-///////////////////////////////////////////////////////
+/*******************************************************************************
+ * Copyright (c) 2017-2019 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre - Initial API and implementation
+ *     Semantum Oy - Improvements
+ *******************************************************************************/
 
 #include "sclpy.h"
 
 #include <windows.h>
 
-jint throwException( JNIEnv *env, char *className, char *message )
+jint throwException( JNIEnv *env, char *className, const char *message )
 {
     jclass exClass = (*env)->FindClass( env, className);
     if (exClass == NULL) {
@@ -24,46 +25,158 @@ jint throwException( JNIEnv *env, char *className, char *message )
     return (*env)->ThrowNew( env, exClass, message );
 }
 
-jint throwPythonException( JNIEnv *env, char *message ) {
+jint throwPythonException( JNIEnv *env, const char *message ) {
        return throwException( env, PYTHON_EXCEPTION, message );
 }
 
-jint throwIllegalArgumentException( JNIEnv *env, char *message ) {
+jint throwIllegalArgumentException( JNIEnv *env, const char *message ) {
        return throwException( env, ILLEGAL_ARGUMENT_EXCEPTION, message );
 }
 
+// Returns a borrowed reference.
+PyObject* getModule(jlong contextID) {
+       return PyState_FindModule((PyModuleDef*) contextID);
+//     return PyImport_AddModule("__main__");
+}
+
 int moduleCount = 0;
 int hasNumpy = 0;
 PyThreadState *main_ts = 0;
 
+static JNIEnv *currentEnv = NULL;
+jobject sclWriter = NULL;
+
+static PyObject *
+writeToSCL(PyObject *self, PyObject *args)
+{
+    if (currentEnv != NULL && sclWriter != NULL) {
+       wchar_t *what;
+               Py_ssize_t length;
+       JNIEnv *env = currentEnv;
+
+               if (!PyArg_ParseTuple(args, "u#", &what, &length))
+                       Py_RETURN_NONE;
+
+               {
+                       PyThreadState *my_ts = PyThreadState_Get();
+                       if (my_ts == main_ts) {
+                               jclass writerClass = (*env)->FindClass(env, WRITER_CLASS);
+                               jmethodID writeMethod = (*env)->GetMethodID(env, writerClass, "write", "([CII)V");
+                               jcharArray chars = (*env)->NewCharArray(env, (jsize)length);
+
+                               (*env)->SetCharArrayRegion(env, chars, 0, length, what);
+                               Py_BEGIN_ALLOW_THREADS
+                               (*env)->CallVoidMethod(env, sclWriter, writeMethod, chars, 0, length);
+                               Py_END_ALLOW_THREADS
+                       } else {
+                               //TODO
+                       }
+               }
+    }
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *
+flushSCL(PyObject *self, PyObject *args)
+{
+    if (currentEnv != NULL && sclWriter != NULL) {
+       JNIEnv *env = currentEnv;
+       PyThreadState *my_ts = PyThreadState_Get();
+       if (my_ts != main_ts) {
+               // TODO: Process calls from other threads
+                       Py_RETURN_NONE;
+       }
+
+       {
+                       jclass writerClass = (*env)->FindClass(env, WRITER_CLASS);
+                       jmethodID flushMethod = (*env)->GetMethodID(env, writerClass, "flush", "()V");
+
+               Py_BEGIN_ALLOW_THREADS
+                       (*env)->CallVoidMethod(env, sclWriter, flushMethod);
+                       Py_END_ALLOW_THREADS
+       }
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef sclWriterMethods[] = {
+    {"write", writeToSCL, METH_VARARGS, "Write something."},
+       {"flush", flushSCL, METH_VARARGS, "Flush output."},
+    {NULL, NULL, 0, NULL}
+};
+
+JNIEXPORT void JNICALL
+Java_org_simantics_pythonlink_PythonContext_initializePython(
+        JNIEnv *env, jobject thisObj, jobject writer) {
+    Py_InitializeEx(0);
+
+    {
+       static struct PyModuleDef moduledef = {
+           PyModuleDef_HEAD_INIT, "sclwriter", NULL, -1, sclWriterMethods
+       };
+       PyObject *m = PyModule_Create(&moduledef);
+
+       sclWriter = (*env)->NewGlobalRef(env, writer);
+
+       if (m == NULL) {
+           throwException(env, PYTHON_EXCEPTION,
+                          "Failed to create SCL writer module");
+       } else {
+           PySys_SetObject("stdout", m);
+           PySys_SetObject("stderr", m);
+           Py_DECREF(m);
+       }
+    }
+
+       hasNumpy = _import_array();
+       hasNumpy = hasNumpy != -1;
+
+       main_ts = PyEval_SaveThread();
+}
+
 JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_createContextImpl(JNIEnv *env, jobject thisObj) {
        char name[16];
 
        if (!main_ts) {
-        Py_Initialize();
-
-       hasNumpy = _import_array();
-       hasNumpy = hasNumpy != -1;
-       main_ts = PyEval_SaveThread();
+               return 0;
        }
 
        sprintf(name, "SCL_%d", ++moduleCount);
 
        PyEval_RestoreThread(main_ts);
        {
-               PyObject *module = PyModule_New(name);
-               PyObject *main = PyImport_AddModule("__main__");
+               PyObject *module;
+               PyModuleDef *modDef = malloc(sizeof(PyModuleDef));
+               memset(modDef, 0, sizeof(PyModuleDef));
+               modDef->m_name = strdup(name);
+               modDef->m_doc = NULL;
+               modDef->m_size = -1;
+
+               module = PyModule_Create(modDef);
+               PyState_AddModule(module, modDef);
+               {
+                       PyObject *builtin = PyImport_AddModule("builtins");
+                       PyObject *dict = PyModule_GetDict(module);
+                       Py_DECREF(module);
+                       PyDict_SetItemString(dict, "__builtin__", builtin);
+                       PyDict_SetItemString(dict, "__builtins__", builtin);
 
-               PyDict_Merge(PyModule_GetDict(module), PyModule_GetDict(main), 0);
-               PyEval_SaveThread();
-               return (jlong)module;
+                       PyEval_SaveThread();
+                       return (jlong)modDef;
+               }
        }
 }
 
-JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_deleteContextImpl(JNIEnv *env, jobject thisObj, jlong contextID) {
-       PyObject *module = (PyObject*)contextID;
+JNIEXPORT void JNICALL
+Java_org_simantics_pythonlink_PythonContext_deleteContextImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID) {
+       PyModuleDef *modDef = (PyModuleDef*)contextID;
        PyEval_RestoreThread(main_ts);
-       Py_XDECREF(module);
+       PyState_RemoveModule(modDef);
+       free((char*)modDef->m_name);
+       free(modDef);
        PyEval_SaveThread();
 }
 
@@ -99,6 +212,7 @@ PyObject *getPythonStringList(JNIEnv *env, jobjectArray value) {
                        PyList_SetItem(result, i, getPythonString(env, (jstring)item));
                }
                else {
+                       Py_INCREF(Py_None);
                        PyList_SetItem(result, i, Py_None);
                }
        }
@@ -311,8 +425,12 @@ PyObject *getPythonRecordObject(JNIEnv *env, jobject object, jobject binding) {
                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);
+               PyObject
+                       *key = getPythonString(env, fieldName),
+                       *item = getPythonObject(env, componentObject, componentBinding);
+               PyDict_SetItem(result, key, item);
+               Py_DECREF(key);
+               Py_DECREF(item);
        }
 
        return result;
@@ -335,8 +453,10 @@ PyObject *getPythonArrayObject(JNIEnv *env, jobject object, jobject binding) {
                jobject item = (*env)->CallObjectMethod(env, binding, getMethod, object, i);
                if (item != NULL)
                        PyList_SetItem(result, i, getPythonObject(env, item, componentBinding));
-               else
+               else {
+                       Py_INCREF(Py_None);
                        PyList_SetItem(result, i, Py_None);
+               }
        }
 
        return result;
@@ -365,7 +485,12 @@ PyObject *getPythonMapObject(JNIEnv *env, jobject object, jobject binding) {
        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));
+               PyObject
+                       *pkey = getPythonObject(env, key, keyBinding),
+                       *pitem = getPythonObject(env, item, valueBinding);
+               PyDict_SetItem(result, pkey, pitem);
+               Py_DECREF(pkey);
+               Py_DECREF(pitem);
        }
 
        (*env)->DeleteLocalRef(env, keys);
@@ -496,6 +621,7 @@ PyObject *getPythonObject(JNIEnv *env, jobject value, jobject binding) {
        }
 }
 
+// Steals refs to name & value.
 void setPythonVariable(PyObject *module, PyObject *name, PyObject *value) {
        if (name && value) {
                PyDict_SetItem(PyModule_GetDict(module), name, value);
@@ -585,9 +711,11 @@ jobjectArray pythonSequenceAsStringArray(JNIEnv *env, PyObject *seq) {
                PyObject *item = PySequence_GetItem(seq, i);
                if (PyUnicode_Check(item)) {
                        jstring value = pythonStringAsJavaString(env, item);
+                       Py_DECREF(item);
                        (*env)->SetObjectArrayElement(env, array, i, value);
                }
                else {
+                       Py_DECREF(item);
                        throwPythonException(env, "List item not a string");
                        return NULL;
                }
@@ -606,10 +734,12 @@ jdoubleArray pythonSequenceAsDoubleArray(JNIEnv *env, PyObject *seq) {
        for (i = 0; i < jlen; i++) {
                PyObject *item = PySequence_GetItem(seq, i);
                if (PyFloat_Check(item)) {
-                       double value = PyFloat_AsDouble(item);
+                       jdouble value = PyFloat_AsDouble(item);
+                       Py_DECREF(item);
                        (*env)->SetDoubleArrayRegion(env, array, i, 1, &value);
                }
                else {
+                       Py_DECREF(item);
                        throwPythonException(env, "List item not a floating point value");
                        return NULL;
                }
@@ -649,6 +779,7 @@ jobjectArray pythonSequenceAsObjectArray(JNIEnv *env, PyObject *seq) {
        for (i = 0; i < jlen; i++) {
                PyObject *item = PySequence_GetItem(seq, i);
                jobject object = pythonObjectAsObject(env, item);
+               Py_DECREF(item);
                (*env)->SetObjectArrayElement(env, array, i, object);
        }
 
@@ -666,9 +797,11 @@ jbooleanArray pythonSequenceAsBooleanArray(JNIEnv *env, PyObject *seq) {
                PyObject *item = PySequence_GetItem(seq, i);
                if (PyBool_Check(item)) {
                        jboolean value = item == Py_True;
+                       Py_DECREF(item);
                        (*env)->SetBooleanArrayRegion(env, array, i, 1, &value);
                }
                else {
+                       Py_DECREF(item);
                        throwPythonException(env, "List item not a boolean");
                        return NULL;
                }
@@ -688,9 +821,11 @@ jintArray pythonSequenceAsIntegerArray(JNIEnv *env, PyObject *seq) {
                PyObject *item = PySequence_GetItem(seq, i);
                if (PyLong_Check(item)) {
                        jint value = PyLong_AsLong(item);
+                       Py_DECREF(item);
                        (*env)->SetIntArrayRegion(env, array, i, 1, &value);
                }
                else {
+                       Py_DECREF(item);
                        throwPythonException(env, "List item not an integer");
                        return NULL;
                }
@@ -710,9 +845,11 @@ jlongArray pythonSequenceAsLongArray(JNIEnv *env, PyObject *seq) {
                PyObject *item = PySequence_GetItem(seq, i);
                if (PyLong_Check(item)) {
                        jlong value = PyLong_AsLongLong(item);
+                       Py_DECREF(item);
                        (*env)->SetLongArrayRegion(env, array, i, 1, &value);
                }
                else {
+                       Py_DECREF(item);
                        throwPythonException(env, "List item not an integer");
                        return NULL;
                }
@@ -785,445 +922,294 @@ jobject pythonDictionaryAsMap(JNIEnv *env, PyObject *dict) {
        return map;
 }
 
-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 = 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);
-}
-
-JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonIntegerArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jintArray value) {
-       PyObject *module = (PyObject*)contextID;
-
-       PyObject *pythonName = getPythonString(env, variableName);
-       PyObject *val = getPythonIntegerList(env, value);
-
-       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;
-
-       PyObject *pythonName = getPythonString(env, variableName);
-       PyObject *val = PyFloat_FromDouble(value);
-
-       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;
-
-       PyObject *pythonName = getPythonString(env, variableName);
-       PyObject *val = getPythonDoubleList(env, value);
-
-       setPythonVariable(module, pythonName, val);
-}
-
-JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonStringVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jstring value) {
-       PyObject *module = (PyObject*)contextID;
-
-       PyObject *pythonName = getPythonString(env, variableName);
-       PyObject *val = getPythonString(env, value);
-
-       setPythonVariable(module, pythonName, val);
-}
-
-JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonStringArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jobjectArray value) {
-       PyObject *module = (PyObject*)contextID;
-
-       PyObject *pythonName = getPythonString(env, variableName);
-       PyObject *val = getPythonStringList(env, value);
-
-       setPythonVariable(module, pythonName, val);
-}
-
-JNIEXPORT void JNICALL Java_org_simantics_pythonlink_PythonContext_setPythonNDArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, jobject value) {
-       PyObject *module = (PyObject*)contextID;
+#define DEF_SETTER(typename, jtype, j2py)                               \
+    JNIEXPORT void JNICALL                                              \
+    Java_org_simantics_pythonlink_PythonContext_setPython##typename     \
+    ##VariableImpl(                                                     \
+        JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName, \
+        jtype value) {                                                  \
+            PyObject *module;                                           \
+                                                                        \
+            PyEval_RestoreThread(main_ts);                              \
+            module = getModule(contextID);                              \
+            setPythonVariable(module, getPythonString(env, variableName), \
+                              j2py(env, value));                        \
+            PyEval_SaveThread();                                        \
+    }
 
+#define getPythonBoolean(env, value) getPythonBool(value)
+#define getPythonLong(env, value) PyLong_FromLongLong(value)
+#define getPythonDouble(env, value) PyFloat_FromDouble(value)
+
+DEF_SETTER(Boolean, jboolean, getPythonBoolean)
+DEF_SETTER(BooleanArray, jbooleanArray, getPythonBooleanList)
+DEF_SETTER(Long, jlong, getPythonLong)
+DEF_SETTER(IntegerArray, jintArray, getPythonIntegerList)
+DEF_SETTER(LongArray, jlongArray, getPythonLongList)
+DEF_SETTER(Double, jdouble, getPythonDouble)
+DEF_SETTER(FloatArray, jfloatArray, getPythonFloatList)
+DEF_SETTER(DoubleArray, jdoubleArray, getPythonDoubleList)
+DEF_SETTER(String, jstring, getPythonString)
+DEF_SETTER(StringArray, jobjectArray, getPythonStringList)
+
+JNIEXPORT void JNICALL
+Java_org_simantics_pythonlink_PythonContext_setPythonNDArrayVariableImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName,
+               jobject value) {
        if (!hasNumpy) {
                throwPythonException(env, "Importing numpy failed");
                return;
        }
 
+       PyEval_RestoreThread(main_ts);
        {
+               PyObject *module = getModule(contextID);
                PyObject *pythonName = getPythonString(env, variableName);
                PyObject *val = getPythonNDArray(env, value);
 
                setPythonVariable(module, pythonName, val);
        }
+       PyEval_SaveThread();
 }
 
-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;
-
-       const char *utfchars = (*env)->GetStringUTFChars(env, statement, NULL);
+JNIEXPORT void JNICALL
+Java_org_simantics_pythonlink_PythonContext_setPythonVariantVariableImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName,
+               jobject value, jobject binding) {
+       PyObject *module;
 
        PyEval_RestoreThread(main_ts);
-       PyErr_Clear();
-       {
-               PyObject *globals;
-
-               globals = PyModule_GetDict(module);
-
-               {
-                       PyObject *result = PyRun_String(utfchars, Py_file_input, globals, globals);
-
-                       PyObject *exceptionType = PyErr_Occurred();
-                       if (exceptionType != NULL) {
-                               PyObject *exception, *traceback;
-                               PyErr_Fetch(&exceptionType, &exception, &traceback);
-
-                               {
-                                       PyObject *tracebackModule = PyImport_ImportModule("traceback");
-                                       if (tracebackModule != NULL) {
-                                               PyObject *formatExc = PyDict_GetItemString(PyModule_GetDict(tracebackModule), "format_exception");
-                                               if (formatExc != NULL) {
-                                                       PyObject *args = PyTuple_Pack(3, exceptionType, exception, traceback);
-                                                       PyObject *message = PyObject_CallObject(formatExc, args);
-                                                       if (message != NULL) {
-                                                               PyObject *emptyStr = PyUnicode_FromString("");
-                                                               PyObject *joined = PyUnicode_Join(emptyStr, message);
-                                                               char *messageStr = PyUnicode_AsUTF8(joined);
-                                                               throwPythonException(env, messageStr);
-                                                               Py_DECREF(joined);
-                                                               Py_DECREF(emptyStr);
-                                                               Py_DECREF(message);
-                                                       }
-                                                       else {
-                                                               throwPythonException(env, "Internal error, no message");
-                                                       }
-                                                       Py_DECREF(args);
-                                                       Py_DECREF(formatExc);
-                                               }
-                                               else {
-                                                       throwPythonException(env, "Internal error, no format_exc function");
-                                               }
-                                               Py_DECREF(tracebackModule);
-                                       }
-                                       else {
-                                               throwPythonException(env, "Internal error, no traceback module");
-                                       }
-                               }
-                       }
-
-                       PyEval_SaveThread();
-                       (*env)->ReleaseStringUTFChars(env, statement, utfchars);
-
-                       return result != NULL ? 0 : 1;
-               }
-       }
+       module = getModule(contextID);
+       setPythonVariable(module, getPythonString(env, variableName),
+                                         getPythonObject(env, value, binding));
+       PyEval_SaveThread();
 }
 
-JNIEXPORT jstring JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonStringVariableImpl(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) {
-               throwPythonException(env, "Python variable not found");
-               return 0;
-       }
-
-       if (!PyUnicode_Check(value)) {
-               throwPythonException(env, "Python variable not a string");
-               return 0;
+static PyObject *getExceptionMessage(PyObject *exceptionType, PyObject *exception, PyObject *traceback) {
+       PyObject *formatExc = NULL, *args = NULL;
+       PyObject *tracebackModule = PyImport_ImportModule("traceback");
+       if (!tracebackModule) {
+               fputs("Python: No traceback module\n", stderr);
+               return NULL;
        }
 
-       {
-               jstring result = pythonStringAsJavaString(env, value);
-               return result;
+       PyErr_NormalizeException(&exceptionType, &exception, &traceback);
+       
+       if (exception && traceback) {
+               formatExc = PyDict_GetItemString(PyModule_GetDict(tracebackModule), "format_exception");
+               args = PyTuple_Pack(3, exceptionType, exception, traceback);
        }
-}
-
-JNIEXPORT jobjectArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonStringArrayVariableImpl(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) {
-               throwPythonException(env, "Python variable not found");
-               return 0;
+       else if (exception) {
+               formatExc = PyDict_GetItemString(PyModule_GetDict(tracebackModule), "format_exception_only");
+               args = PyTuple_Pack(2, exceptionType, exception);
        }
 
-       if (!PySequence_Check(value)) {
-               throwPythonException(env, "Python variable not a sequence");
-               return 0;
-       }
+       if (formatExc != NULL && args != NULL) {
+               PyObject *result = PyObject_CallObject(formatExc, args);
+               if (!result) {
+                       fputs("Python: No result from format_exception\n", stderr);
+                       // Fallback to a direct string representation of the exception object
+                       result = PyObject_Str(exception);
+               }
+               Py_XDECREF(args);
+               // Py_XDECREF(formatExc) - Borrowed reference
+               Py_DECREF(tracebackModule);
 
-       {
-               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;
+       else {
+               if (!formatExc) fputs("Python: No format_exception\n", stderr);
 
-       PyObject *pythonName = getPythonString(env, variableName);
+               Py_XDECREF(args);
+               // Py_XDECREF(formatExc) - Borrowed reference
+               Py_DECREF(tracebackModule);
 
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return 0;
-       }
-
-       if (!PyBool_Check(value)) {
-               throwPythonException(env, "Python variable not a boolean");
-               return 0;
+               return NULL;
        }
-
-       return value == Py_True;
 }
 
-JNIEXPORT jbooleanArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonBooleanArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
-       PyObject *module = (PyObject*)contextID;
+static void throwExceptionType(JNIEnv *env, PyObject *exceptionType) {
+       PyObject *ty_name = exceptionType ? PyObject_GetAttrString(exceptionType, "__name__") : NULL;
+       PyObject *str = ty_name ? PyUnicode_AsEncodedString(ty_name, "utf-8", "ignore") : NULL;
 
-       PyObject *pythonName = getPythonString(env, variableName);
+       throwPythonException(env, str ? PyBytes_AsString(str) : "Internal error - no exception type");
 
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return 0;
-       }
-
-       if (!PySequence_Check(value)) {
-               throwPythonException(env, "Python variable not a sequence");
-               return 0;
-       }
-
-       {
-               jbooleanArray result = pythonSequenceAsBooleanArray(env, value);
-               return result;
-       }
+       Py_XDECREF(str);
+       Py_XDECREF(ty_name);
 }
 
-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);
-
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return 0;
-       }
-
-       if (!PyLong_Check(value)) {
-               throwPythonException(env, "Python variable not an integer");
-               return 0;
-       }
+JNIEXPORT jint JNICALL
+Java_org_simantics_pythonlink_PythonContext_executePythonStatementImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID, jstring statement) {
+       const char *utfchars = (*env)->GetStringUTFChars(env, statement, NULL);
 
+       PyEval_RestoreThread(main_ts);
+       PyErr_Clear();
        {
-               jlong result = PyLong_AsLongLong(value);
-               return result;
-       }
-}
+               PyObject *module = getModule(contextID);
 
-JNIEXPORT jintArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonIntegerArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
-       PyObject *module = (PyObject*)contextID;
+               PyObject *globals;
 
-       PyObject *pythonName = getPythonString(env, variableName);
+               globals = PyModule_GetDict(module);
 
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return NULL;
-       }
+               currentEnv = env;
 
-       if (!PySequence_Check(value)) {
-               throwPythonException(env, "Python variable not a sequence");
-               return NULL;
-       }
+               {
+                       // PyObject *result = PyRun_String(utfchars, Py_file_input, globals, globals); - Not available in Py_LIMITED_API
+                       PyObject *code = Py_CompileString(utfchars, "SCL_INPUT", Py_file_input);
+                       PyObject *result = code ? PyEval_EvalCode(code,globals, globals) : NULL;
+                       PyObject *exceptionType = PyErr_Occurred();
 
-       {
-               jintArray result = pythonSequenceAsIntegerArray(env, value);
-               return result;
-       }
-}
+                       if (exceptionType != NULL) {
+                               PyObject *exception, *traceback, *message;
+                               PyErr_Fetch(&exceptionType, &exception, &traceback);
 
-JNIEXPORT jlongArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonLongArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
-       PyObject *module = (PyObject*)contextID;
+                               message = getExceptionMessage(exceptionType, exception, traceback);
+                               if (message != NULL) {
+                                       if (PyList_Check(message)) {
+                                               PyObject *emptyStr = PyUnicode_FromString("");
+                                               PyObject *temp = PyUnicode_Join(emptyStr, message);
+                                               if (temp) {
+                                                       Py_DECREF(message);
+                                                       message = temp;
+                                               }
 
-       PyObject *pythonName = getPythonString(env, variableName);
+                                               Py_DECREF(emptyStr);
+                                       }
 
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return NULL;
-       }
+                                       if (!PyUnicode_Check(message)) {
+                                               PyObject *temp = PyObject_Str(message);
+                                               if (temp) {
+                                                       Py_DECREF(message);
+                                                       message = temp;
+                                               }
+                                       }
 
-       if (!PySequence_Check(value)) {
-               throwPythonException(env, "Python variable not a sequence");
-               return NULL;
-       }
+                                       PyObject* str = PyUnicode_AsEncodedString(message, "utf-8", "ignore");
+                                       Py_DECREF(message);
 
-       {
-               jlongArray result = pythonSequenceAsLongArray(env, value);
-               return result;
-       }
-}
+                                       if (str != NULL) {
+                                               throwPythonException(env, PyBytes_AsString(str));
+                                               Py_DECREF(str);
+                                       }
+                                       else {
+                                               fputs("Python: Encoding message string failed\n", stderr);
+                                               throwExceptionType(env, exceptionType);
+                                       }
+                               }
+                               else {
+                                       fputs("Python: No exception message\n", stderr);
+                                       throwExceptionType(env, exceptionType);
+                               }
+                       }
 
-JNIEXPORT jdouble JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonDoubleVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
-       PyObject *module = (PyObject*)contextID;
+                       Py_XDECREF(result);
+                       Py_XDECREF(code);
 
-       PyObject *pythonName = getPythonString(env, variableName);
+                       PyEval_SaveThread();
+                       (*env)->ReleaseStringUTFChars(env, statement, utfchars);
 
-       PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-       if (value == NULL) {
-               throwPythonException(env, "Python variable not found");
-               return 0.0;
-       }
+                       currentEnv = NULL;
 
-       if (!PyFloat_Check(value)) {
-               throwPythonException(env, "Python variable not a float");
-               return 0.0;
-       }
-
-       {
-               jdouble result = PyFloat_AsDouble(value);
-               return result;
+                       return result != NULL ? 0 : 1;
+               }
        }
 }
 
-JNIEXPORT jdoubleArray JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonDoubleArrayVariableImpl(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) {
-               throwPythonException(env, "Python variable not found");
-               return NULL;
-       }
-
-       if (!PySequence_Check(value)) {
-               throwPythonException(env, "Python variable not a sequence");
-               return NULL;
-       }
+// Returns a borrowed reference.
+static PyObject *getPythonValue(
+               JNIEnv *env, jlong contextID, jstring variableName) {
+    PyObject *module = getModule(contextID);
+    PyObject *pythonName = getPythonString(env, variableName);
+    PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
 
-       {
-               jdoubleArray result = pythonSequenceAsDoubleArray(env, value);
-               return result;
-       }
-}
+    Py_DECREF(pythonName);
+    if (value == NULL) {
+        throwPythonException(env, "Python variable not found");
+    }
+    return value;
+}
+
+#define DEF_GETTER(typename, jtype, check, desc, py2j)                  \
+    JNIEXPORT jtype JNICALL                                             \
+    Java_org_simantics_pythonlink_PythonContext_getPython##typename     \
+    ##VariableImpl(                                                     \
+            JNIEnv *env, jobject thisObj, jlong contextID,              \
+            jstring variableName) {                                     \
+        jtype result = 0;                                               \
+        PyEval_RestoreThread(main_ts);                                  \
+        do {                                                            \
+            PyObject *value = getPythonValue(env, contextID, variableName); \
+            if (value == 0) break;                                      \
+            if (check(value)) {                                         \
+                result = py2j(env, value);                              \
+            } else {                                                    \
+                throwPythonException(env, "Python variable not " desc); \
+            }                                                           \
+        } while (0);                                                    \
+        PyEval_SaveThread();                                            \
+        return result;                                                  \
+    }
 
-JNIEXPORT jobject JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonNDArrayVariableImpl(JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
-       PyObject *module = (PyObject*)contextID;
+#define pythonBoolAsJboolean(env, value) ((value) == Py_True)
+#define pythonLongAsJlong(env, value) PyLong_AsLongLong(value)
+#define pythonFloatAsJdouble(env, value) PyFloat_AsDouble(value)
+
+DEF_GETTER(String, jstring, PyUnicode_Check, "a string",
+                  pythonStringAsJavaString)
+DEF_GETTER(StringArray, jobjectArray, PySequence_Check, "a sequence",
+                  pythonSequenceAsStringArray)
+DEF_GETTER(Boolean, jboolean, PyBool_Check, "a Boolean", pythonBoolAsJboolean)
+DEF_GETTER(BooleanArray, jbooleanArray, PySequence_Check, "a sequence",
+                  pythonSequenceAsBooleanArray)
+DEF_GETTER(Long, jlong, PyLong_Check, "an integer", pythonLongAsJlong)
+DEF_GETTER(IntegerArray, jintArray, PySequence_Check, "a sequence",
+                  pythonSequenceAsIntegerArray)
+DEF_GETTER(LongArray, jlongArray, PySequence_Check, "a sequence",
+                  pythonSequenceAsLongArray)
+DEF_GETTER(Double, jdouble, PyFloat_Check, "a float", pythonFloatAsJdouble)
+DEF_GETTER(DoubleArray, jdoubleArray, PySequence_Check, "a sequence",
+                  pythonSequenceAsDoubleArray)
+
+JNIEXPORT jobject JNICALL
+Java_org_simantics_pythonlink_PythonContext_getPythonNDArrayVariableImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
+       jobject result = NULL;
 
        if (!hasNumpy) {
                throwPythonException(env, "Importing numpy failed");
                return NULL;
        }
 
-       {
-               PyObject *pythonName = getPythonString(env, variableName);
-
-               PyObject *value = PyDict_GetItem(PyModule_GetDict(module), pythonName);
-               if (value == NULL) {
-                       throwPythonException(env, "Python variable not found");
-                       return NULL;
-               }
-
+       PyEval_RestoreThread(main_ts);
+       do {
+               PyObject *value = getPythonValue(env, contextID, variableName);
+               if (value == NULL) break;
                if (!PyArray_Check(value)) {
                        throwPythonException(env, "Python variable not an ndarray");
-                       return NULL;
-               }
-
-               if (PyArray_TYPE((PyArrayObject*)value) != NPY_DOUBLE) {
-                       throwPythonException(env, "Only ndarrays of type double are supported");
-                       return NULL;
-               }
-
-               {
-                       jobject result = pythonArrayAsNDArray(env, (PyArrayObject *)value);
-                       return result;
+               } else if (PyArray_TYPE((PyArrayObject*)value) != NPY_DOUBLE) {
+                       throwPythonException(
+                                       env, "Only ndarrays of type double are supported");
+               } else {
+                       result = pythonArrayAsNDArray(env, (PyArrayObject *)value);
                }
-       }
-}
-
-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) {
-               throwPythonException(env, "Python variable not found");
-               return NULL;
-       }
-
-       hasNumpy = _import_array() != -1;
-
-       {
-               jobject result = pythonObjectAsObject(env, value);
-               return result;
-       }
+       } while (0);
+       PyEval_SaveThread();
+       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);
+#define python_anything_goes(value) 1
 
-       PyObject *pythonName = getPythonString(env, variableName);
-
-       if (!PyDict_Contains(dict, pythonName)) {
-               return 0;
-       }
+DEF_GETTER(Variant, jobject, python_anything_goes, "frabjous",
+                  pythonObjectAsObject)
 
+JNIEXPORT jint JNICALL
+Java_org_simantics_pythonlink_PythonContext_getPythonVariableTypeImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID, jstring variableName) {
+       jint result = 0;
+       PyEval_RestoreThread(main_ts);
        {
-               PyObject *value = PyDict_GetItem(dict, pythonName);
-
-               jint result;
-
+               PyObject *value = getPythonValue(env, contextID, variableName);
                if (PyBool_Check(value))
                        result = 1;
                else if (PyLong_Check(value))
@@ -1242,28 +1228,36 @@ JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_getPythonVari
                        result = 8;
                else
                        result = -1;
-
-               return result;
        }
+       PyEval_SaveThread();
+       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);
+JNIEXPORT jobjectArray JNICALL
+Java_org_simantics_pythonlink_PythonContext_getPythonVariableNamesImpl(
+               JNIEnv *env, jobject thisObj, jlong contextID) {
+       jobjectArray result = NULL;
+       PyEval_RestoreThread(main_ts);
+       {
+               PyObject *module = getModule(contextID);
+               PyObject *dict = PyModule_GetDict(module);
 
-       jobjectArray result = (*env)->NewObjectArray(env, (jsize)size, (*env)->FindClass(env, STRING_CLASS), NULL);
+               PyObject *keys = PyDict_Keys(dict);
+               Py_ssize_t size = PyList_Size(keys);
+               Py_ssize_t i;
 
-       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);
-       }
+               result = (*env)->NewObjectArray(
+                               env, (jsize)size, (*env)->FindClass(env, STRING_CLASS), NULL);
 
-       Py_XDECREF(keys);
+               for (i = 0; i < size; i++) {
+                       jstring javaName = pythonStringAsJavaString(
+                                       env, PyList_GetItem(keys, i));
+                       (*env)->SetObjectArrayElement(env, result, (jint)i, javaName);
+               }
 
+               Py_XDECREF(keys);
+       }
+       PyEval_SaveThread();
        return result;
 }