]> gerrit.simantics Code Review - simantics/python.git/blobdiff - org.simantics.pythonlink/src/org/simantics/pythonlink/PythonContext.java
Support for output and exceptions without tracebacks
[simantics/python.git] / org.simantics.pythonlink / src / org / simantics / pythonlink / PythonContext.java
index 16b02348196b4326f70f4475173a94677928c433..62703f0795f0d80c6b915cf5c4022d7cfa3c71cd 100644 (file)
@@ -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<Listener> 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);