From: Reino Ruusu Date: Wed, 1 Feb 2017 19:17:18 +0000 (+0200) Subject: Support for output and exceptions without tracebacks X-Git-Tag: v1.31.0~2 X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fpython.git;a=commitdiff_plain;h=8885425046e0f89893c0e1ee0fe5c27948dcd2be Support for output and exceptions without tracebacks Added a redundant implementation of SCLReportingWriter for backward compatibility. Added support for a user-provided Writer object in executePythonStatement. Moved instantiation of SCLReportingWriter to Java. Added support for sys.stdout.flush() in Python. Change-Id: Icf53ed000844d136b93aa86f7bf41db3559f8748 --- diff --git a/org.simantics.pythonlink.win32.x86_64/jnipython.dll b/org.simantics.pythonlink.win32.x86_64/jnipython.dll index ab792ab..bb0a05a 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 428a45a..1592a2f 100644 --- a/org.simantics.pythonlink.win32.x86_64/src/sclpy.c +++ b/org.simantics.pythonlink.win32.x86_64/src/sclpy.c @@ -48,10 +48,10 @@ static PyObject * writeToSCL(PyObject *self, PyObject *args) { if (currentEnv != NULL && sclWriter != NULL) { - JNIEnv *env = currentEnv; - Py_UNICODE *what; Py_ssize_t length; + JNIEnv *env = currentEnv; + if (!PyArg_ParseTuple(args, "u#", &what, &length)) Py_RETURN_NONE; @@ -75,30 +75,62 @@ writeToSCL(PyObject *self, PyObject *args) 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_Initialize(); -JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_createContextImpl(JNIEnv *env, jobject thisObj) { - char name[16]; + { + static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "sclwriter", NULL, -1, sclWriterMethods, }; + PyObject *m = PyModule_Create(&moduledef); - if (!main_ts) { - Py_Initialize(); + sclWriter = (*env)->NewGlobalRef(env, writer); + + if (m == NULL) throwException(env, PYTHON_EXCEPTION, "Failed to create SCL writer module"); + + PySys_SetObject("stdout", m); + PySys_SetObject("stderr", m); + } + + hasNumpy = _import_array(); + hasNumpy = hasNumpy != -1; - { - static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "sclwriter", NULL, -1, sclWriterMethods, }; - PyObject *m = PyModule_Create(&moduledef); + main_ts = PyEval_SaveThread(); +} - if (m == NULL) throwException(env, PYTHON_EXCEPTION, "Failed to create SCL writer module"); - PySys_SetObject("stdout", m); - PySys_SetObject("stderr", m); - } +JNIEXPORT jlong JNICALL Java_org_simantics_pythonlink_PythonContext_createContextImpl(JNIEnv *env, jobject thisObj) { + char name[16]; - hasNumpy = _import_array(); - hasNumpy = hasNumpy != -1; - main_ts = PyEval_SaveThread(); + if (!main_ts) { + return 0; } sprintf(name, "SCL_%d", ++moduleCount); @@ -921,6 +953,37 @@ Java_org_simantics_pythonlink_PythonContext_setPythonVariantVariableImpl( PyEval_SaveThread(); } +static PyObject *getExceptionMessage(PyObject *exceptionType, PyObject *exception, PyObject *traceback) { + PyObject *formatExc = NULL, *args = NULL; + PyObject *tracebackModule = PyImport_ImportModule("traceback"); + if (!tracebackModule) { + return NULL; + } + + if (exception && traceback) { + formatExc = PyDict_GetItemString(PyModule_GetDict(tracebackModule), "format_exception"); + args = PyTuple_Pack(3, exceptionType, exception, traceback); + } + else if (exception) { + formatExc = PyDict_GetItemString(PyModule_GetDict(tracebackModule), "format_exception_only"); + args = PyTuple_Pack(2, exceptionType, exception); + } + + Py_DECREF(tracebackModule); + + if (formatExc != NULL && args != NULL) { + PyObject *result = PyObject_CallObject(formatExc, args); + Py_XDECREF(args); + Py_XDECREF(formatExc); + return result; + } + else { + Py_XDECREF(args); + Py_XDECREF(formatExc); + return NULL; + } +} + JNIEXPORT jint JNICALL Java_org_simantics_pythonlink_PythonContext_executePythonStatementImpl( JNIEnv *env, jobject thisObj, jlong contextID, jstring statement) { @@ -931,63 +994,38 @@ Java_org_simantics_pythonlink_PythonContext_executePythonStatementImpl( { PyObject *module = getModule(contextID); - jclass sclReportingWriterClass = (*env)->FindClass(env, SCL_REPORTING_WRITER_CLASS); - jmethodID constructor = (*env)->GetMethodID(env, sclReportingWriterClass, "", "()V"); - jmethodID flushMethod = (*env)->GetMethodID(env, sclReportingWriterClass, "flush", "()V"); - PyObject *globals; globals = PyModule_GetDict(module); currentEnv = env; - if (sclReportingWriterClass && constructor) - sclWriter = (*env)->NewObject(env, sclReportingWriterClass, constructor); - else - sclWriter = NULL; { PyObject *result = PyRun_String(utfchars, Py_file_input, globals, globals); PyObject *exceptionType = PyErr_Occurred(); if (exceptionType != NULL) { - PyObject *exception, *traceback; + PyObject *exception, *traceback, *message; 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 { - PyTypeObject - *ty = (PyTypeObject *)exceptionType; - throwPythonException( - env, ty ? ty->tp_name - : "Internal error, null exception type"); - } - 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"); - } + message = getExceptionMessage(exceptionType, exception, traceback); + 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 { + PyTypeObject + *ty = (PyTypeObject *)exceptionType; + throwPythonException( + env, ty ? ty->tp_name + : "Internal error, null exception type"); } + Py_XDECREF(exceptionType); Py_XDECREF(exception); Py_XDECREF(traceback); @@ -996,12 +1034,7 @@ Java_org_simantics_pythonlink_PythonContext_executePythonStatementImpl( PyEval_SaveThread(); (*env)->ReleaseStringUTFChars(env, statement, utfchars); - if (sclWriter != NULL) { - (*env)->CallVoidMethod(env, sclWriter, flushMethod); - } - currentEnv = NULL; - sclWriter = NULL; return result != NULL ? 0 : 1; } diff --git a/org.simantics.pythonlink.win32.x86_64/src/sclpy.h b/org.simantics.pythonlink.win32.x86_64/src/sclpy.h index 15d681d..e71e107 100644 --- a/org.simantics.pythonlink.win32.x86_64/src/sclpy.h +++ b/org.simantics.pythonlink.win32.x86_64/src/sclpy.h @@ -1,118 +1,117 @@ -#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. - */ - -#ifdef BUILD_DLL - #define DLL_EXPORT __declspec(dllexport) -#else - #define DLL_EXPORT __declspec(dllimport) -#endif - - -#define JAVA_MAXINT (0x7fffffff) - -#define PYTHON_EXCEPTION "org/simantics/pythonlink/PythonException" -#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 WRITER_CLASS "java/io/Writer" -#define SCL_REPORTING_WRITER_CLASS "org/simantics/scl/runtime/reporting/SCLReportingWriter" - -#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__ +#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. + */ + +#ifdef BUILD_DLL + #define DLL_EXPORT __declspec(dllexport) +#else + #define DLL_EXPORT __declspec(dllimport) +#endif + + +#define JAVA_MAXINT (0x7fffffff) + +#define PYTHON_EXCEPTION "org/simantics/pythonlink/PythonException" +#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 WRITER_CLASS "java/io/Writer" + +#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/src/org/simantics/pythonlink/PythonContext.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java index 16b0234..62703f0 100644 --- a/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java @@ -1,6 +1,9 @@ package org.simantics.pythonlink; import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; @@ -14,18 +17,57 @@ import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.scl.runtime.SCLContext; -import org.simantics.scl.runtime.reporting.SCLReportingHandler; public class PythonContext implements Closeable { - private long contextID; + protected static final String PYTHON_SCL_WRITER = "python.scl.writer"; + //TODO Replace with a count of open contexts and call Py_Finalize when the last one closes. + private static Boolean isPyInitialized = false; + + // A writer that is called by the sys.stdout and sys.stderr streams in Python + private static final Writer pythonWriter = new Writer() { + Writer defaultWriter = new OutputStreamWriter(System.out); + + @Override + public void close() throws IOException { + throw new IllegalStateException("This writer should never be closed!"); + } + + @Override + public void flush() throws IOException { + Writer writer = getPythonWriter(); + synchronized (writer) { + writer.flush(); + } + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + Writer writer = getPythonWriter(); + synchronized (writer) { + writer.write(cbuf, off, len); + } + } + + // Get a thread-specific Writer instance + private Writer getPythonWriter() { + SCLContext sclContext = SCLContext.getCurrent(); + Writer writer = (Writer) sclContext.get(PYTHON_SCL_WRITER); + return writer != null ? writer : defaultWriter; + } + }; - public interface Listener { + private static synchronized void ensurePythonInit() { + if (!isPyInitialized) { + execute(() -> initializePython(pythonWriter)); + isPyInitialized = true; + } + } + + public interface Listener { void updated(String variableName); void closed(); } - static ExecutorService pythonExecutor = Executors.newSingleThreadExecutor(); - Set listeners = new HashSet<>(); public enum VariableType { @@ -43,8 +85,15 @@ public class PythonContext implements Closeable { static Pattern namePattern = Pattern.compile("([a-zA-Z_][a-zA-Z_0-9]*)"); - PythonContext() { + // Really a C pointer. + private long contextID; + + PythonContext() { + ensurePythonInit(); contextID = execute(() -> createContextImpl()); + if (contextID == 0) { + throw new PythonException("Python initialization has failed"); + } } public void addListener(Listener listener) { @@ -56,7 +105,7 @@ public class PythonContext implements Closeable { } @Override - public void close() { + public synchronized void close() { long id = contextID; contextID = 0; if (id != 0) execute(() -> deleteContextImpl(id)); @@ -76,18 +125,35 @@ public class PythonContext implements Closeable { close(); } - public void executePythonStatement(String statement) { + public void executePythonStatement(String statement, Writer writer) { SCLContext sclContext = SCLContext.getCurrent(); execute(() -> { SCLContext.push(sclContext); - executePythonStatementImpl( contextID, statement ); + Writer oldWriter = (Writer) sclContext.put(PYTHON_SCL_WRITER, writer); + try { + executePythonStatementImpl( contextID, statement ); + pythonWriter.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (oldWriter != null) { + sclContext.put(PYTHON_SCL_WRITER, oldWriter); + } + else { + sclContext.remove(PYTHON_SCL_WRITER); + } + } SCLContext.pop(); }); for (Listener l : listeners) { l.updated(null); } } + public void executePythonStatement(String statement) { + executePythonStatement(statement, new SCLReportingWriter()); + } + // Setters public void setPythonBooleanVariable(String variableName, boolean value) { @@ -247,7 +313,9 @@ public class PythonContext implements Closeable { throw new IllegalArgumentException("Invalid Python variable name " + variableName); } - static void execute(Runnable job) { + static final ExecutorService pythonExecutor = Executors.newSingleThreadExecutor(); + + static void execute(Runnable job) { try { pythonExecutor.submit(job).get(); } catch (InterruptedException | ExecutionException e) { @@ -264,6 +332,8 @@ public class PythonContext implements Closeable { } // Native function declarations + private static native void initializePython(Writer writer); + private static native long createContextImpl(); private static native void deleteContextImpl(long contextID); diff --git a/org.simantics.pythonlink/src/org/simantics/pythonlink/SCLReportingWriter.java b/org.simantics.pythonlink/src/org/simantics/pythonlink/SCLReportingWriter.java new file mode 100644 index 0000000..0bab712 --- /dev/null +++ b/org.simantics.pythonlink/src/org/simantics/pythonlink/SCLReportingWriter.java @@ -0,0 +1,46 @@ +package org.simantics.pythonlink; + +import java.io.Writer; + +import org.simantics.scl.runtime.reporting.SCLReporting; + +public class SCLReportingWriter extends Writer { + + StringBuilder builder = null; + + public SCLReportingWriter() { + builder = new StringBuilder(); + } + + @Override + public void close() { + flush(); + } + + @Override + public void flush() { + if (builder.length() > 0) { + SCLReporting.print(builder.toString()); + builder.setLength(0); + } + } + + @Override + public void write(char[] buf, int off, int len) { + if (len == 0) return; + + if (len < 0) throw new IllegalArgumentException("Negative buffer region length"); + if (off < 0) throw new IllegalArgumentException("Negative buffer region offset"); + if (off + len > buf.length) throw new IllegalArgumentException("Buffer region overflow"); + + for (int i = 0; i < len; i++) { + if (buf[off + i] == '\n') { + SCLReporting.print(builder.toString()); + builder.setLength(0); + } + else { + builder.append(buf[off + i]); + } + } + } +}