| /***************************************************************************** |
| * Copyright (c) 2019 CEA LIST and others. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * CEA LIST - Initial API and implementation |
| * |
| *****************************************************************************/ |
| package org.eclipse.papyrus.ease.lang.python.jupyter.internal; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.ease.AbstractReplScriptEngine; |
| import org.eclipse.ease.Logger; |
| import org.eclipse.ease.Script; |
| import org.eclipse.ease.ScriptEngineException; |
| import org.eclipse.ease.ScriptExecutionException; |
| import org.eclipse.ease.debugging.ScriptStackTrace; |
| import org.eclipse.ease.lang.python.py4j.internal.IInteractiveReturn; |
| import org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine; |
| import org.eclipse.ease.tools.RunnableWithResult; |
| import org.eclipse.papyrus.ease.lang.python.jupyter.Activator; |
| import org.eclipse.swt.widgets.Display; |
| |
| public class JupyterEngine extends AbstractReplScriptEngine { |
| |
| |
| /** |
| * The ID of the Engine, to match the declaration in the plugin.xml |
| */ |
| public static final String ENGINE_ID = "org.eclipse.ease.lang.python.jupyter.engine"; |
| |
| |
| |
| |
| private static final String NOTE_BOOK_EXTENSION = "ipynb"; |
| |
| |
| |
| |
| private static final long PYTHON_SHUTDOWN_TIMEOUT_SECONDS = 5; |
| |
| |
| |
| |
| protected IPythonSideEngine fPythonSideEngine; |
| private Process fPythonProcess; |
| private Thread fInputGobbler, fErrorGobbler; |
| |
| private JupyterProxy jupyterProxy; |
| |
| /** |
| * Standard StreamGobbler. |
| */ |
| private static class StreamGobbler implements Runnable { |
| private final InputStream fReader; |
| private final OutputStream fWriter; |
| private final String fStreamName; |
| |
| public StreamGobbler(final InputStream stream, final OutputStream output, final String streamName) { |
| fReader = stream; |
| fWriter = output; |
| fStreamName = streamName; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| final byte[] bytes = new byte[512]; |
| int readCount; |
| while ((readCount = fReader.read(bytes)) >= 0) { |
| try { |
| fWriter.write(bytes, 0, readCount); |
| } catch (final IOException e) { |
| Logger.error(Activator.PLUGIN_ID, "Failed to write data read from Python's " + fStreamName + " stream.", e); |
| } |
| } |
| } catch (final IOException e) { |
| Logger.error(Activator.PLUGIN_ID, "Failed to read data from Python's " + fStreamName + " stream.", e); |
| } |
| } |
| } |
| |
| public JupyterEngine() { |
| super("Jupyter (CEA)"); |
| } |
| |
| @Override |
| protected void setupEngine() throws ScriptEngineException { |
| |
| if( fPythonSideEngine != null ) |
| return; |
| |
| if (jupyterProxy == null) { |
| initializeJupyterProxy(); |
| |
| } |
| try { |
| setTerminateOnIdle(false); |
| |
| fPythonProcess = jupyterProxy.getJupyterProcess( ); |
| Activator.getDefault().registeProxy(jupyterProxy); |
| |
| fInputGobbler = new Thread(new StreamGobbler(fPythonProcess.getInputStream(), getOutputStream(), "stdout"), |
| "EASE py4j engine output stream gobbler"); |
| fInputGobbler.start(); |
| |
| |
| fErrorGobbler = new Thread(new StreamGobbler(fPythonProcess.getErrorStream(), getErrorStream(), "stderr"), |
| "EASE py4j engine error stream gobbler"); |
| fErrorGobbler.start(); |
| |
| |
| jupyterProxy.waitForKernelStartup(); |
| |
| fPythonSideEngine = jupyterProxy.getPythonSideEngine(); |
| |
| |
| } catch (final Exception e) { |
| teardownEngine(); |
| throw new ScriptEngineException("Failed to start Python process. Please check the setting for the Python interpreter" |
| + " in Preferences -> Scripting -> Python Scripting:\n" + e.getMessage(), e); |
| } |
| } |
| |
| |
| protected Script notebookScript; |
| |
| |
| private void initializeJupyterProxy() { |
| jupyterProxy = new JupyterProxy(); |
| |
| List<Script> scriptToRemove = new ArrayList<>(); |
| for (Script script : getScheduledScripts()) { |
| if (script.getCommand() instanceof IFile ) { |
| IFile scriptFile = (IFile) script.getCommand(); |
| if (NOTE_BOOK_EXTENSION.equals(scriptFile.getFileExtension())){ |
| jupyterProxy.setNotebook(scriptFile); |
| scriptToRemove.add(script); |
| notebookScript = script; |
| } |
| } |
| |
| } |
| getScheduledScripts().removeAll(scriptToRemove); |
| |
| List<Integer> cellsToRun = new ArrayList<>(); |
| Object argObj = getVariable("argv"); |
| if( argObj instanceof String[]) { |
| for (String arg : (String[])argObj) { |
| try { |
| Integer cellNumber = Integer.parseInt(arg); |
| cellsToRun.add(cellNumber); |
| }catch (NumberFormatException e) { |
| continue; |
| } |
| |
| } |
| } |
| jupyterProxy.addAutoCells(cellsToRun); |
| } |
| |
| |
| @Override |
| protected Object execute(final Script script, final Object reference, final String fileName, final boolean uiThread) throws Throwable { |
| if (uiThread) { |
| // run in UI thread |
| final RunnableWithResult<Object> runnable = new RunnableWithResult<Object>() { |
| |
| @Override |
| public Object runWithTry() throws Throwable { |
| return internalExecute(script, fileName); |
| } |
| }; |
| |
| Display.getDefault().syncExec(runnable); |
| return runnable.getResultOrThrow(); |
| |
| } else { |
| return internalExecute(script, fileName); |
| } |
| } |
| |
| protected Object internalExecute(final Script script, final String fileName) throws Throwable, Exception { |
| IInteractiveReturn interactiveReturn; |
| if (script.isShellMode()) { |
| interactiveReturn = fPythonSideEngine.executeInteractive(script.getCode()); |
| } else { |
| final String code = script.getCode(); |
| interactiveReturn = fPythonSideEngine.executeScript(code, fileName); |
| } |
| final Object exception = interactiveReturn.getException(); |
| if (exception instanceof Throwable) { |
| throw (Throwable) exception; |
| } else if (exception != null) { |
| throw new ScriptExecutionException(exception.toString(), 0, null, null, new ScriptStackTrace(), null); |
| } else { |
| return interactiveReturn.getResult(); |
| } |
| } |
| |
| @Override |
| public void terminateCurrent() { |
| if (notebookScript != null) { |
| notebookScript.setResult(0); |
| } |
| setTerminateOnIdle(true); |
| |
| } |
| |
| @Override |
| protected void teardownEngine() { |
| |
| |
| // TODO: this clean shutdown isn't working as intended. |
| // Sometimes (on Linux) the Python process seems to shutdown |
| // before fully acknowledging the call to teardownEngine, leaving |
| // us in a worst state than if we shutdown not-cleanly. |
| // When/if this is resurrected, the fPythonProcess.destroy(); |
| // below should be removed. |
| // if (fPythonSideEngine != null) { |
| // // try a clean shutdown |
| // fPythonSideEngine.teardownEngine(); |
| // } |
| |
| |
| |
| if (jupyterProxy != null) { |
| jupyterProxy.shutDown(); |
| } |
| |
| Activator.getDefault().unRegisterProxy(jupyterProxy); |
| |
| |
| try { |
| // Wait until the gobblers have shovelled all their |
| // inputs before allowing the engine to considered terminated |
| if (fInputGobbler != null) { |
| fInputGobbler.join(); |
| } |
| if (fErrorGobbler != null) { |
| fErrorGobbler.join(); |
| } |
| } catch (final InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| |
| @Override |
| public void registerJar(final URL url) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| protected Object internalGetVariable(final String name) { |
| return fPythonSideEngine.internalGetVariable(name); |
| } |
| |
| @Override |
| protected Map<String, Object> internalGetVariables() { |
| return fPythonSideEngine.internalGetVariables(); |
| } |
| |
| @Override |
| protected boolean internalHasVariable(final String name) { |
| return fPythonSideEngine.internalHasVariable(name); |
| } |
| |
| @Override |
| protected void internalSetVariable(final String name, final Object content) { |
| fPythonSideEngine.internalSetVariable(name, content); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public <T> T getAdapter(final Class<T> adapter) { |
| System.out.print("Adapted to: " + adapter); |
| if (adapter.isInstance(fPythonProcess)) { |
| System.out.println(" SUCCESS"); |
| return (T) fPythonProcess; |
| } |
| System.out.println(" Failed"); |
| return super.getAdapter(adapter); |
| } |
| |
| @Override |
| public String toString(Object object) { |
| if (object == null) |
| return "None"; |
| |
| return super.toString(object); |
| } |
| |
| public Process getPythonProcess() { |
| return fPythonProcess; |
| } |
| |
| public void setJupyterProxy(JupyterProxy jupyterProxy) { |
| this.jupyterProxy= jupyterProxy; |
| |
| } |
| } |