| /*=============================================================================# |
| # Copyright (c) 2009, 2018 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.rj.server.client; |
| |
| import static org.eclipse.statet.rj.server.util.ServerUtils.MISSING_ANSWER_STATUS; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInput; |
| import java.io.OutputStream; |
| import java.rmi.ConnectException; |
| import java.rmi.RemoteException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.ScheduledFuture; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.locks.Condition; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| |
| import org.eclipse.statet.rj.RjException; |
| import org.eclipse.statet.rj.data.REnvironment; |
| import org.eclipse.statet.rj.data.RJIO; |
| import org.eclipse.statet.rj.data.RJIOExternalizable; |
| import org.eclipse.statet.rj.data.RList; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RReference; |
| import org.eclipse.statet.rj.server.BinExchange; |
| import org.eclipse.statet.rj.server.ComHandler; |
| import org.eclipse.statet.rj.server.ConsoleReadCmdItem; |
| import org.eclipse.statet.rj.server.CtrlCmdItem; |
| import org.eclipse.statet.rj.server.DataCmdItem; |
| import org.eclipse.statet.rj.server.DataCmdItem.Operation; |
| import org.eclipse.statet.rj.server.DbgCmdItem; |
| import org.eclipse.statet.rj.server.DbgCmdItem.CustomDataReader; |
| import org.eclipse.statet.rj.server.ExtUICmdItem; |
| import org.eclipse.statet.rj.server.GDCmdItem; |
| import org.eclipse.statet.rj.server.GraOpCmdItem; |
| import org.eclipse.statet.rj.server.MainCmdC2SList; |
| import org.eclipse.statet.rj.server.MainCmdItem; |
| import org.eclipse.statet.rj.server.MainCtrlCmdItem; |
| import org.eclipse.statet.rj.server.REngine; |
| import org.eclipse.statet.rj.server.RjsComConfig; |
| import org.eclipse.statet.rj.server.RjsComObject; |
| import org.eclipse.statet.rj.server.RjsPing; |
| import org.eclipse.statet.rj.server.RjsStatus; |
| import org.eclipse.statet.rj.server.Server; |
| import org.eclipse.statet.rj.server.client.RClientGraphic.InitConfig; |
| import org.eclipse.statet.rj.server.dbg.CtrlReport; |
| import org.eclipse.statet.rj.services.RPlatform; |
| import org.eclipse.statet.rj.services.RService; |
| |
| |
| /** |
| * Generic RJ Com protocol client for servers offering a {@link REngine}. |
| * <p> |
| * It offers basic implementation for most methods of the {@link RService} API, |
| * including:</p> |
| * <ul> |
| * <li>Expression evaluation</li> |
| * <li>Data exchange to assign or eval/read {@link RObject}</li> |
| * <li>File exchange to write/read file on the server (must be enabled at server side)</li> |
| * <li>R graphics (requires an {@link RClientGraphicFactory}, set in {@link #initGraphicFactory()}, |
| * or via {@link #setGraphicFactory(RClientGraphicFactory, RClientGraphicActions)})</li> |
| * <li>Console (REPL), if connected to server slot 0</li> |
| * </ul> |
| * <p> |
| * If offers two mechanisms allowing the usage of RService API and R thread when they are already |
| * used by a regular call. So the two modes allows to "hijack" the R thread to "inject" additional |
| * calls to the RService API:</p> |
| * <ul> |
| * <li>Hot Mode: Enables to run code in any situation, also if R is busy. Nesting of hot modes is |
| * not possible. It can be requested asynchronous from any thread. The hot mode is appropriate |
| * for e.g. short task for the GUI.</li> |
| * <li>Extra Mode: Enables to run code at the before or after of a client-server communication. |
| * Must be requested synchronous from the R thread.</li> |
| * </ul> |
| */ |
| public abstract class AbstractRJComClient implements ComHandler { |
| |
| |
| public static final String RJ_CLIENT_ID= "org.eclipse.statet.rj.client"; |
| |
| public static int[] version() { |
| return new int[] { 3, 0, 0 }; |
| } |
| |
| public static final int EXTRA_BEFORE= 1; |
| public static final int EXTRA_NESTED= 2; |
| |
| private static final ScheduledExecutorService RJHelper_EXECUTOR = |
| Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("RJHelper")); |
| |
| private static final Random RAND= new Random(); |
| |
| |
| private static class DummyFactory implements RClientGraphicFactory { |
| |
| @Override |
| public Map<String, ? extends Object> getInitServerProperties() { |
| return null; |
| } |
| |
| @Override |
| public RClientGraphic newGraphic(final int devId, final double w, final double h, |
| final InitConfig config, |
| final boolean active, final RClientGraphicActions actions, final int options) { |
| return new RClientGraphicDummy(devId, w, h); |
| } |
| |
| @Override |
| public void closeGraphic(final RClientGraphic graphic) { |
| } |
| |
| } |
| |
| private final static class RunnableList { |
| |
| private Runnable[] array; |
| private int size; |
| |
| public RunnableList() { |
| this.array= new Runnable[4]; |
| this.size= 0; |
| } |
| |
| |
| public void add(final Runnable value) { |
| if (value == null) { |
| throw new IllegalArgumentException(); |
| } |
| final int oldCapacity= this.array.length; |
| if (this.size < oldCapacity) { |
| this.array[this.size++]= value; |
| return; |
| } |
| final Runnable[] newArray= new Runnable[oldCapacity + 4]; |
| System.arraycopy(this.array, 0, newArray, 0, oldCapacity); |
| newArray[this.size++]= value; |
| this.array= newArray; |
| } |
| |
| public boolean isNotEmpty() { |
| return (this.size != 0); |
| } |
| |
| public Runnable[] consume() { |
| final Runnable[] oldListeners= this.array; |
| this.size= 0; |
| this.array= new Runnable[4]; |
| return oldListeners; |
| } |
| |
| } |
| |
| |
| private class KeepAliveRunnable implements Runnable { |
| |
| @Override |
| public void run() { |
| runAsyncPing(); |
| } |
| |
| } |
| |
| private class HotModeRequestRunnable implements Runnable { |
| |
| @Override |
| public void run() { |
| if (AbstractRJComClient.this.hotModeRequested.get()) { |
| try { |
| runAsyncCtrl(CtrlCmdItem.REQUEST_HOT_MODE); |
| } |
| catch (final CoreException e) { |
| if (e.getStatus().getSeverity() != IStatus.CANCEL) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when requesting hot mode.", e)); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| private RService rService; |
| private Object rHandle; |
| |
| private IProgressMonitor progressMonitor; |
| |
| private final RJIO mainIO= new RJIO(); |
| private MainCmdItem mainC2SFirst; |
| private final MainCmdC2SList mainC2SList= new MainCmdC2SList(this.mainIO); |
| private final RunnableList mainDeferredCmds= new RunnableList(); |
| private boolean mainRunGC; |
| |
| private boolean consoleReadCallbackRequired; |
| private ConsoleReadCmdItem consoleReadCallback; |
| |
| private final Object platformLock= new Object(); |
| private Map<String, Object> platformData; |
| private RPlatform platformObj; |
| |
| private final byte randomId= (byte) (0xff & RAND.nextInt(255)); |
| |
| private int dataLevelRequest= 0; |
| private int dataLevelAnswer= 0; |
| private int dataLevelIgnore= 0; |
| private byte dataRequestCounter= (byte) (0xff & RAND.nextInt(255)); |
| private final int[] dataRequestId= new int[32]; |
| private final MainCmdItem[] dataAnswer= new MainCmdItem[32]; |
| |
| private boolean runFinishTask; |
| |
| private boolean dbgOpRequest; |
| private DbgCmdItem dbgOpAnswer; |
| private CustomDataReader dbgOpCustomReader; |
| |
| private int hotModeState; |
| private ConsoleReadCmdItem hotModeReadCallbackBackup; |
| private MainCmdItem hotModeC2SFirstBackup; |
| private final AtomicBoolean hotModeRequested= new AtomicBoolean(); |
| private final Runnable hotModeRunnable= new HotModeRequestRunnable(); |
| |
| private int extraModeRequested; |
| |
| private RClientGraphicFactory graphicFactory; |
| private RClientGraphicActions graphicActions; |
| private final RClientGraphic graphicDummy= new RClientGraphicDummy(1, 0, 0); |
| private RClientGraphic[] graphics= new RClientGraphic[16]; |
| private int currentGraphicOptions; |
| private RClientGraphic lastGraphic; |
| |
| private REngine rjConsoleEngine; |
| |
| private List<Runnable> defferedRunnables; |
| |
| private boolean closed; |
| private ScheduledFuture<?> keepAliveJob; |
| private String closedMessage= "Connection to R engine is closed."; |
| |
| private final Lock clientWaitLock= new ReentrantLock(); |
| private final Condition clientWaitCondition= this.clientWaitLock.newCondition(); |
| private final List<Callable<Boolean>> cancelHandler= new ArrayList<>(); |
| |
| |
| protected AbstractRJComClient() { |
| } |
| |
| |
| public void initClient(final Object rHandle, final RService r, |
| final Map<String, Object> properties, final int id) { |
| this.rHandle= rHandle; |
| this.rService= r; |
| properties.put("rj.com.init", Boolean.TRUE); //$NON-NLS-1$ |
| properties.put(RjsComConfig.RJ_COM_S2C_ID_PROPERTY_ID, id); |
| |
| try { |
| initGraphicFactory(); |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when initializing R client graphic factory.", e)); |
| } |
| if (this.graphicFactory != null) { |
| final Map<String, ? extends Object> additional= this.graphicFactory.getInitServerProperties(); |
| if (additional != null) { |
| properties.putAll(additional); |
| } |
| } |
| else { |
| this.graphicFactory= new DummyFactory(); |
| log(new Status(IStatus.WARNING, RJ_CLIENT_ID, -1, "No R client graphic factory configured.", null)); |
| } |
| } |
| |
| public void setDbgCustomCmdReader(final DbgCmdItem.CustomDataReader reader) { |
| this.dbgOpCustomReader= reader; |
| } |
| |
| public final void setServer(final REngine rjServer, final int client) { |
| final List<Runnable> runnables; |
| synchronized (this) { |
| this.rjConsoleEngine= rjServer; |
| if (client == 0) { |
| long t= 45 * 1000; |
| try { |
| final String p= System.getProperty("org.eclipse.statet.rj.client.keepaliveInterval"); //$NON-NLS-1$ |
| if (p != null && p.length() > 0) { |
| t= Long.parseLong(p); |
| } |
| } |
| catch (final Exception e) {} |
| this.keepAliveJob= RJHelper_EXECUTOR.scheduleWithFixedDelay( |
| new KeepAliveRunnable(), t, t, TimeUnit.MILLISECONDS ); |
| } |
| runnables= this.defferedRunnables; |
| this.defferedRunnables= null; |
| } |
| if (runnables != null) { |
| for (int i= 0; i < runnables.size(); i++) { |
| RJHelper_EXECUTOR.execute(runnables.get(i)); |
| } |
| } |
| } |
| |
| public Object getRHandle() { |
| return this.rHandle; |
| } |
| |
| public RService getRService() { |
| return this.rService; |
| } |
| |
| public final REngine getConsoleEngine() { |
| return this.rjConsoleEngine; |
| } |
| |
| protected void execAsync(final Runnable runnable) { |
| synchronized (this) { |
| if (this.rjConsoleEngine == null) { |
| if (this.defferedRunnables == null) { |
| this.defferedRunnables= new ArrayList<>(8); |
| } |
| this.defferedRunnables.add(runnable); |
| return; |
| } |
| } |
| RJHelper_EXECUTOR.execute(runnable); |
| } |
| |
| protected void initGraphicFactory() { |
| } |
| |
| |
| public final void setGraphicFactory(final RClientGraphicFactory factory, final RClientGraphicActions actions) { |
| if (factory == null) { |
| throw new NullPointerException(); |
| } |
| this.graphicFactory= factory; |
| this.graphicActions= actions; |
| } |
| |
| public boolean isClosed() { |
| return this.closed; |
| } |
| |
| public void setClosed(final boolean closed) { |
| if (this.closed != closed) { |
| this.closed= closed; |
| if (closed) { |
| final ScheduledFuture<?> job= this.keepAliveJob; |
| if (job != null) { |
| this.keepAliveJob= null; |
| job.cancel(true); |
| } |
| } |
| } |
| } |
| |
| public void setRjsProperties(final Map<String, ? extends Object> properties) throws CoreException { |
| try { |
| this.rjConsoleEngine.setProperties(properties); |
| } |
| catch (final RemoteException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RJ_CLIENT_ID, "An error occurred when setting server properties.", e)); |
| } |
| } |
| |
| |
| @Override |
| public final void processMainCmd(final ObjectInput in) throws IOException { |
| boolean runGC= false; |
| updateBusy(in.readBoolean()); |
| if (this.hotModeState == 4) { |
| this.hotModeState= 0; |
| this.consoleReadCallback= this.hotModeReadCallbackBackup; |
| this.hotModeReadCallbackBackup= null; |
| if (this.hotModeC2SFirstBackup != null) { |
| addC2SCmd(this.hotModeC2SFirstBackup); |
| this.hotModeC2SFirstBackup= null; |
| } |
| } |
| |
| this.mainIO.connect(in); |
| final int check= this.mainIO.readCheck1(); |
| |
| while (true) { |
| final byte type= in.readByte(); |
| switch (type) { |
| case MainCmdItem.T_NONE: |
| this.mainIO.readCheck2(check); |
| this.mainIO.disconnect(in); |
| this.mainRunGC= runGC; |
| return; |
| case MainCmdItem.T_CONSOLE_READ_ITEM: |
| processPrompt(new ConsoleReadCmdItem(this.mainIO)); |
| continue; |
| case MainCmdItem.T_CONSOLE_WRITE_ITEM: |
| runGC= true; |
| writeConsoleOutput(this.mainIO.readByte(), this.mainIO.readString()); |
| continue; |
| case MainCmdItem.T_MESSAGE_ITEM: |
| runGC= true; |
| showMessage(this.mainIO.readString()); |
| continue; |
| case MainCmdItem.T_EXTENDEDUI_ITEM: |
| runGC= true; |
| processUICallback(this.mainIO); |
| continue; |
| case MainCmdItem.T_GRAPH_ITEM: |
| runGC= true; |
| processGDCmd(this.mainIO); |
| continue; |
| case MainCmdItem.T_MAIN_CTRL_ITEM: |
| runGC= true; |
| processMainCtrlCmd(this.mainIO); |
| continue; |
| case MainCmdItem.T_DATA_ITEM: |
| runGC= true; |
| processDataCmd(this.mainIO); |
| continue; |
| case MainCmdItem.T_GRAPHICS_OP_ITEM: |
| runGC= true; |
| processGraphicsOpCmd(this.mainIO); |
| continue; |
| case MainCmdItem.T_DBG_ITEM: |
| runGC= true; |
| processDbgCmd(this.mainIO); |
| continue; |
| default: |
| this.mainIO.disconnect(in); |
| throw new IOException("Unknown cmdtype id: " + type); |
| } |
| } |
| } |
| |
| private final void processCmdDeferred(final Runnable runnable) { |
| this.mainDeferredCmds.add(runnable); |
| } |
| |
| |
| public final boolean processUICallback(final RJIO io) throws IOException { |
| final ExtUICmdItem item= new ExtUICmdItem(io); |
| try { |
| final RList answer= handleUICallback(item.getDataText(), item.getDataArgs(), |
| this.progressMonitor); |
| item.setAnswer(answer); |
| } |
| catch (final IOException e) { |
| throw e; |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when exec RJ UI command '" + item.getDataText() + "'.", e)); |
| if (item.waitForClient()) { |
| item.setAnswer(new RjsStatus(RjsStatus.ERROR, 0, "Client error processing current command.")); |
| } |
| } |
| if (item.waitForClient()) { |
| addC2SCmd(item); |
| return true; |
| } |
| return false; |
| } |
| |
| protected RList handleUICallback(final String commandId, final RList args, |
| final IProgressMonitor monitor) throws Exception { |
| throw new CoreException(new Status(IStatus.WARNING, RJ_CLIENT_ID, -1, "Unhandled RJ UI command '" + commandId + "'.", null)); |
| } |
| |
| |
| public final void processGDCmd(final RJIO io) throws IOException { |
| final int devId; |
| final int options; |
| { final int i= io.readInt(); |
| options= (i & MainCmdItem.OV_WAITFORCLIENT); |
| devId= (i & ~MainCmdItem.OV_WAITFORCLIENT); |
| } |
| byte requestId= -1; |
| try { |
| switch (io.readByte()) { |
| case GDCmdItem.C_NEW_PAGE: |
| addGraphic(devId, |
| io.readDouble(), |
| io.readDouble(), |
| io.readInt(), |
| io.readBoolean() ); |
| return; |
| case GDCmdItem.C_CLOSE_DEVICE: |
| removeGraphic(devId); |
| return; |
| case GDCmdItem.C_GET_SIZE: |
| addC2SCmd(new GDCmdItem.DoubleAnswer(requestId= io.readByte(), |
| devId, getGraphic(devId).computeSize() )); |
| return; |
| case GDCmdItem.C_SET_ACTIVE_OFF: |
| getGraphic(devId).setActive(false); |
| return; |
| case GDCmdItem.C_SET_ACTIVE_ON: |
| getGraphic(devId).setActive(true); |
| return; |
| case GDCmdItem.C_SET_MODE: |
| getGraphic(devId).setMode(io.readByte()); |
| return; |
| case GDCmdItem.C_GET_FONTMETRIC: |
| addC2SCmd(new GDCmdItem.DoubleAnswer(requestId= io.readByte(), |
| devId, getGraphic(devId).computeFontMetric( |
| io.readInt() ))); |
| return; |
| case GDCmdItem.C_GET_STRINGWIDTH: |
| addC2SCmd(new GDCmdItem.DoubleAnswer(requestId= io.readByte(), |
| devId, getGraphic(devId).computeStringWidth( |
| io.readString() ))); |
| return; |
| |
| case GDCmdItem.SET_CLIP: |
| getGraphic(devId).addSetClip( |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble() ); |
| return; |
| case GDCmdItem.SET_COLOR: |
| getGraphic(devId).addSetColor( |
| io.readInt() ); |
| return; |
| case GDCmdItem.SET_FILL: |
| getGraphic(devId).addSetFill( |
| io.readInt() ); |
| return; |
| case GDCmdItem.SET_LINE: |
| getGraphic(devId).addSetLine( |
| io.readInt(), |
| io.readFloat(), |
| io.readByte(), |
| io.readByte(), |
| io.readFloat() ); |
| return; |
| case GDCmdItem.SET_FONT: |
| getGraphic(devId).addSetFont( |
| io.readString(), |
| io.readInt(), |
| io.readFloat(), |
| io.readFloat() ); |
| return; |
| |
| case GDCmdItem.DRAW_LINE: |
| getGraphic(devId).addDrawLine( |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble() ); |
| return; |
| case GDCmdItem.DRAW_RECTANGLE: |
| getGraphic(devId).addDrawRect( |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble() ); |
| return; |
| case GDCmdItem.DRAW_POLYLINE: |
| getGraphic(devId).addDrawPolyline( |
| io.readDoubleArray(), |
| io.readDoubleArray2() ); |
| return; |
| case GDCmdItem.DRAW_POLYGON: |
| getGraphic(devId).addDrawPolygon( |
| io.readDoubleArray(), |
| io.readDoubleArray2() ); |
| return; |
| case GDCmdItem.DRAW_PATH: |
| getGraphic(devId).addDrawPath( |
| io.readIntArray(), |
| io.readDoubleArray(), |
| io.readDoubleArray2(), |
| io.readInt() ); |
| return; |
| case GDCmdItem.DRAW_CIRCLE: |
| getGraphic(devId).addDrawCircle( |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble() ); |
| return; |
| case GDCmdItem.DRAW_TEXT: |
| getGraphic(devId).addDrawText( |
| io.readString(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble() ); |
| return; |
| case GDCmdItem.DRAW_RASTER: |
| getGraphic(devId).addDrawRaster( |
| io.readByteArray(), |
| io.readBoolean(), |
| io.readInt(), |
| io.readInt(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readDouble(), |
| io.readBoolean() ); |
| return; |
| |
| case GDCmdItem.CAPTURE: |
| addC2SCmd(new GDCmdItem.Answer(requestId= io.readByte(), |
| devId, getGraphic(devId).capture( |
| io.readInt(), |
| io.readInt() ))); |
| return; |
| |
| case GDCmdItem.U_LOCATOR: { |
| final byte fid= requestId= io.readByte(); |
| processCmdDeferred(new Runnable() { |
| @Override |
| public void run() { |
| addC2SCmd(new GDCmdItem.DoubleAnswer(fid, |
| devId, getGraphic(devId).runRLocator( |
| AbstractRJComClient.this.rService, |
| AbstractRJComClient.this.progressMonitor ))); |
| } |
| }); |
| return; } |
| |
| default: |
| if ((options & MainCmdItem.OV_WAITFORCLIENT) != 0) { |
| requestId= io.readByte(); |
| } |
| throw new UnsupportedOperationException("Unknown GD command."); |
| } |
| } |
| catch (final IOException e) { |
| throw e; |
| } |
| catch (final Throwable e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when processing graphic command.", e)); |
| if (requestId >= 0) { |
| addC2SCmd(new GDCmdItem.Answer(requestId, devId, new RjsStatus(RjsStatus.ERROR, 0))); |
| } |
| // throw new RuntimeException(e); |
| } |
| } |
| |
| |
| private final void processMainCtrlCmd(final RJIO io) throws IOException { |
| try { |
| final MainCtrlCmdItem item= new MainCtrlCmdItem(io); |
| addDataAnswer(item); |
| } |
| catch (final IOException e) { |
| throw e; |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when processing control command answer.", e)); |
| } |
| } |
| |
| private final void processDataCmd(final RJIO io) throws IOException { |
| try { |
| final DataCmdItem item= new DataCmdItem(io); |
| addDataAnswer(item); |
| } |
| catch (final IOException e) { |
| throw e; |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when processing data command answer.", e)); |
| } |
| } |
| |
| private final void processGraphicsOpCmd(final RJIO io) throws IOException { |
| try { |
| final GraOpCmdItem item= new GraOpCmdItem(io); |
| addDataAnswer(item); |
| } |
| catch (final IOException e) { |
| throw e; |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when processing graphic operation answer.", e)); |
| } |
| } |
| |
| private final int newDataLevel() { |
| final int level= ++this.dataLevelRequest; |
| if (level >= this.dataAnswer.length) { |
| this.dataLevelRequest--; |
| throw new UnsupportedOperationException("too much nested operations"); |
| } |
| this.dataLevelAnswer= 0; |
| |
| return level; |
| } |
| |
| private final MainCmdItem createDataRequestId(final int level, final MainCmdItem item) { |
| this.dataRequestId[level]= ((((0xff) & item.getCmdType()) << 24) |
| | ((0xff & item.getOp()) << 16) |
| | ((0xff & this.randomId << 8)) |
| | ((0xff & ++this.dataRequestCounter)) ); |
| item.requestId= (((0xff & level) << 24) | (0xffffff & this.dataRequestId[level])); |
| return item; |
| } |
| |
| private final void addDataAnswer(final MainCmdItem item) throws RjException { |
| final int level= (((0xff000000) & item.requestId) >>> 24); |
| if (level > 0 && level <= this.dataLevelRequest |
| && this.dataRequestId[level] == (((0xff & item.getCmdType()) << 24) |
| | (0xffffff & item.requestId)) ) { |
| this.dataAnswer[level]= item; |
| this.dataLevelAnswer= level; |
| return; |
| } |
| if ((item.requestId & 0xff00 >>> 8) != (this.randomId & 0xff)) { |
| // other client |
| // System.out.println("Other client: " + item); |
| return; |
| } |
| throw new RjException("Unexpected server answer: " + item); |
| } |
| |
| private final void finalizeDataLevel() { |
| final int level= this.dataLevelRequest--; |
| this.dataAnswer[level]= null; |
| this.dataLevelAnswer= (this.dataAnswer[this.dataLevelRequest] != null) ? this.dataLevelRequest : 0; |
| |
| this.runFinishTask= true; |
| } |
| |
| public final int getDataLevel() { |
| return this.dataLevelRequest; |
| } |
| |
| |
| protected final void processDbgCmd(final RJIO io) throws IOException { |
| final DbgCmdItem item= new DbgCmdItem(io, this.dbgOpCustomReader); |
| if (item.getOp() > DbgCmdItem.OP_C2S_S2C) { |
| handleDbgEvent(item.getOp(), item.getData()); |
| } |
| else if (this.dbgOpRequest) { |
| this.dbgOpAnswer= item; |
| } |
| } |
| |
| protected void handleDbgEvent(final byte dbgOp, final Object event) { |
| } |
| |
| |
| protected abstract void log(IStatus status); |
| |
| protected abstract void handleServerStatus(final RjsStatus serverStatus, final IProgressMonitor monitor) throws CoreException; |
| |
| protected abstract void handleStatus(Status status, IProgressMonitor monitor); |
| |
| protected void scheduleConnectionCheck() { |
| } |
| |
| |
| public final boolean runAsyncPing() { |
| try { |
| final RjsComObject answer= this.rjConsoleEngine.runAsync(RjsPing.INSTANCE); |
| if (answer instanceof RjsStatus) { |
| final RjsStatus status= (RjsStatus) answer; |
| if (status.getSeverity() == RjsStatus.OK) { |
| return true; |
| } |
| scheduleConnectionCheck(); |
| } |
| } |
| catch (final ConnectException e) { |
| scheduleConnectionCheck(); |
| } |
| catch (final RemoteException e) { |
| // TODO if (rmiregistry is available) scheduleCheck(); |
| // no need to log here |
| } |
| catch (final Exception e) { |
| } |
| return false; |
| } |
| |
| public final boolean runAsyncInterrupt() { |
| final Callable<Boolean>[] handlers= getCancelHandlers(); |
| for (int i= handlers.length - 1; i >= 0; i--) { |
| try { |
| final Boolean done= handlers[i].call(); |
| if (done != null && done.booleanValue()) { |
| return false; |
| } |
| } |
| catch (final Exception e) { |
| // handler failed |
| } |
| } |
| final IProgressMonitor monitor= this.progressMonitor; |
| if (monitor != null) { |
| monitor.setCanceled(true); |
| } |
| try { |
| runAsyncCtrl(CtrlCmdItem.REQUEST_CANCEL); |
| return true; |
| } |
| catch (final CoreException e) { |
| if (e.getStatus().getSeverity() != IStatus.CANCEL) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "An error occurred when trying to interrupt R.", e)); |
| } |
| return false; |
| } |
| } |
| |
| public final void runAsyncCtrl(final int id) throws CoreException { |
| final RjsStatus status= (RjsStatus) runAsync(new CtrlCmdItem(id)); |
| if (status.getSeverity() != RjsStatus.OK) { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Executing CTRL command failed.", null)); |
| } |
| } |
| |
| public final RjsComObject runAsync(final RjsComObject com) throws CoreException { |
| if (this.closed) { |
| throw new CoreException(new Status(IStatus.ERROR, RJ_CLIENT_ID, 0, this.closedMessage, null)); |
| } |
| try { |
| return this.rjConsoleEngine.runAsync(com); |
| } |
| catch (final Exception e) { |
| throw new CoreException(new Status(IStatus.ERROR, RJ_CLIENT_ID, 0, "Communication error.", e)); |
| } |
| } |
| |
| public final void runMainLoopPing(final IProgressMonitor monitor) throws CoreException { |
| try { |
| this.mainRunGC= false; |
| final RjsStatus status= (RjsStatus) this.rjConsoleEngine.runMainLoop(RjsPing.INSTANCE); |
| if (status.getSeverity() == RjsStatus.OK) { |
| return; |
| } |
| handleServerStatus(status, monitor); |
| } |
| catch (final ConnectException e) { |
| handleServerStatus(new RjsStatus(RjsStatus.INFO, Server.S_DISCONNECTED), monitor); |
| } |
| catch (final Exception e) { |
| // no need to log here |
| handleServerStatus(new RjsStatus(RjsStatus.INFO, Server.S_LOST), monitor); |
| } |
| } |
| |
| public final void runMainLoop(RjsComObject sendCom, MainCmdItem sendItem, final IProgressMonitor monitor) throws CoreException { |
| if (this.closed) { |
| throw new CoreException(new Status(IStatus.ERROR, RJ_CLIENT_ID, 0, this.closedMessage, null)); |
| } |
| final boolean loopReadCallbackIgnore= !this.consoleReadCallbackRequired; |
| final int loopDataLevelIgnore= this.dataLevelIgnore; |
| try { |
| if ((this.extraModeRequested & EXTRA_BEFORE) != 0 && this.hotModeState < 1) { |
| this.extraModeRequested= 0; |
| this.dataLevelIgnore= this.dataLevelRequest; |
| processExtraMode(EXTRA_BEFORE); |
| } |
| while (this.hotModeRequested.get() |
| && this.hotModeState < 1 && !this.dbgOpRequest) { |
| this.dataLevelIgnore= this.dataLevelRequest; |
| startHotMode(); |
| } |
| this.progressMonitor= monitor; |
| int ok= 0; |
| while (!this.closed) { |
| try { |
| RjsComObject receivedCom= null; |
| if (sendItem != null) { |
| if (sendItem.getCmdType() == MainCmdItem.T_CONSOLE_READ_ITEM) { |
| this.consoleReadCallback= null; |
| } |
| this.mainC2SList.setObjects(sendItem); |
| sendCom= this.mainC2SList; |
| sendItem= null; |
| } |
| // System.out.println("client *-> server: " + sendCom); |
| this.mainRunGC= false; |
| receivedCom= this.rjConsoleEngine.runMainLoop(sendCom); |
| this.mainC2SList.clear(); |
| sendCom= null; |
| // System.out.println("client *<- server: " + receivedCom); |
| switch (receivedCom.getComType()) { |
| case RjsComObject.T_PING: |
| ok= 0; |
| sendCom= RjsStatus.OK_STATUS; |
| continue; |
| case RjsComObject.T_MAIN_LIST: |
| if (this.mainDeferredCmds.isNotEmpty()) { |
| final Runnable[] runnables= this.mainDeferredCmds.consume(); |
| for (int i= 0; i < runnables.length; i++) { |
| if (runnables[i] != null) { |
| try { |
| runnables[i].run(); |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, 0, |
| "An error occurred when running a deferred command.", e )); |
| } |
| } |
| } |
| } |
| ok= 0; |
| switch (this.hotModeState) { |
| case 1: |
| sendCom= this.mainC2SList; // repeat |
| continue; |
| case 2: |
| this.hotModeState= 3; |
| this.hotModeRequested.set(false); |
| this.dataLevelIgnore= this.dataLevelRequest; |
| try { |
| processHotMode(); |
| continue; |
| } |
| finally { |
| this.progressMonitor= monitor; |
| this.hotModeState= 4; |
| this.consoleReadCallback.setAnswer(RjsStatus.OK_STATUS); |
| sendItem= this.consoleReadCallback; |
| } |
| default: |
| while ((sendItem= getC2SCmds()) == null |
| && (loopReadCallbackIgnore || this.consoleReadCallback != null) |
| && (this.dataLevelRequest <= loopDataLevelIgnore |
| || this.dataLevelRequest == this.dataLevelAnswer |
| || (this.extraModeRequested & EXTRA_NESTED) != 0 ) |
| && (this.dbgOpRequest == (this.dbgOpAnswer != null))) { |
| if (this.mainRunGC) { |
| this.mainRunGC= false; |
| this.rjConsoleEngine.runMainLoop(RjsPing.INSTANCE); |
| } |
| if ((this.extraModeRequested & EXTRA_NESTED) != 0 && this.hotModeState < 1) { |
| this.extraModeRequested= 0; |
| this.dataLevelIgnore= this.dataLevelRequest; |
| try { |
| processExtraMode(EXTRA_NESTED); |
| continue; // validate again |
| } |
| finally { |
| this.progressMonitor= monitor; |
| } |
| } |
| else { |
| return; // finished |
| } |
| } |
| continue; |
| } |
| case RjsComObject.T_STATUS: |
| ok= 0; |
| processStatus((RjsStatus) receivedCom, monitor); |
| sendCom= this.mainC2SList; |
| continue; |
| } |
| } |
| catch (final ConnectException e) { |
| handleServerStatus(new RjsStatus(RjsStatus.INFO, Server.S_DISCONNECTED), monitor); |
| return; |
| } |
| catch (final RemoteException e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "Communication error detail. Send:\n"+sendCom, e)); |
| if (!this.closed && runAsyncPing()) { // async to avoid server gc |
| if (this.consoleReadCallback == null && ok == 0) { |
| ok++; |
| handleStatus(new Status(IStatus.ERROR, RJ_CLIENT_ID, "Communication error, see Eclipse log for detail."), monitor); |
| continue; |
| } |
| throw new CoreException(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, "Communication error.", e)); |
| } |
| handleServerStatus(new RjsStatus(RjsStatus.INFO, Server.S_LOST), monitor); |
| return; |
| } |
| } |
| |
| handleServerStatus(new RjsStatus(RjsStatus.INFO, Server.S_DISCONNECTED), monitor); |
| } |
| finally { |
| this.dataLevelIgnore= loopDataLevelIgnore; |
| } |
| } |
| |
| |
| protected final void addC2SCmd(final MainCmdItem item) { |
| if (this.mainC2SFirst == null) { |
| this.mainC2SFirst= item; |
| } |
| else { |
| item.next= this.mainC2SFirst; |
| this.mainC2SFirst= item; |
| |
| // TODO |
| log(new Status(IStatus.INFO, RJ_CLIENT_ID, "Multiple C2S items:\\" + this.mainC2SFirst.toString() + "\n" + this.mainC2SFirst.next.toString())); |
| } |
| } |
| |
| private final MainCmdItem getC2SCmds() { |
| final MainCmdItem item= this.mainC2SFirst; |
| this.mainC2SFirst= null; |
| return item; |
| } |
| |
| private final void processStatus(final RjsStatus status, final IProgressMonitor monitor) |
| throws CoreException { |
| if ((status.getCode() & 0xffffff00) == 0) { |
| handleServerStatus(status, monitor); |
| return; |
| } |
| if (status.getSeverity() != RjsStatus.OK) { |
| // TODO |
| // System.out.println(status); |
| } |
| } |
| |
| private final void processPrompt(final ConsoleReadCmdItem item) { |
| switch (item.getCmdOption() & 0xf) { |
| case 2: |
| if (this.hotModeState < 2) { |
| this.hotModeState= 2; |
| this.hotModeReadCallbackBackup= this.consoleReadCallback; |
| this.hotModeC2SFirstBackup= this.mainC2SFirst; |
| this.mainC2SFirst= null; |
| } |
| this.consoleReadCallback= item; |
| return; |
| case RjsComObject.V_TRUE: |
| this.consoleReadCallback= item; |
| updatePrompt(item.getDataText(), true); |
| return; |
| default: |
| this.consoleReadCallback= item; |
| updatePrompt(item.getDataText(), false); |
| return; |
| } |
| } |
| |
| public void requestHotMode(final boolean async) { |
| this.hotModeRequested.set(true); |
| if (async) { |
| RJHelper_EXECUTOR.schedule(this.hotModeRunnable, 100, TimeUnit.MILLISECONDS); |
| } |
| } |
| |
| public boolean startHotMode() { |
| if (this.hotModeState == 0) { |
| this.hotModeRequested.set(false); |
| final boolean savedCallbackRequired= this.consoleReadCallbackRequired; |
| this.consoleReadCallbackRequired= false; |
| try { |
| this.hotModeState= 1; |
| runMainLoop(new CtrlCmdItem(CtrlCmdItem.REQUEST_HOT_MODE), null, new NullProgressMonitor()); |
| return true; |
| } |
| catch (final Throwable e) { |
| this.hotModeState= 0; |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, 0, |
| "An error occurred when running hot mode.", e )); |
| } |
| finally { |
| this.consoleReadCallbackRequired= savedCallbackRequired; |
| } |
| } |
| return false; |
| } |
| |
| public void requestExtraMode(final int positions) { |
| this.extraModeRequested= positions; |
| } |
| |
| |
| protected void updateBusy(final boolean isBusy) { |
| } |
| |
| protected void updatePrompt(final String text, final boolean addToHistory) { |
| } |
| |
| protected void writeConsoleOutput(final byte streamId, final String text) { |
| } |
| |
| protected void showMessage(final String text) { |
| } |
| |
| protected void processHotMode() { |
| } |
| |
| protected void processExtraMode(final int i) { |
| } |
| |
| private void addGraphic(final int devId, final double w, final double h, final int canvasColor, |
| final boolean activate) throws RjException { |
| if (devId >= 0) { |
| if (devId >= this.graphics.length) { |
| final RClientGraphic[] newArray= new RClientGraphic[devId + 10]; |
| System.arraycopy(this.graphics, 0, newArray, 0, this.graphics.length); |
| this.graphics= newArray; |
| } |
| final InitConfig config= new InitConfig(); |
| config.canvasColor= canvasColor; |
| if (this.graphics[devId] != null) { |
| this.graphics[devId].reset(w, h, config); |
| this.graphics[devId].setActive(activate); |
| } |
| else { |
| this.graphics[devId]= this.lastGraphic= this.graphicFactory.newGraphic(devId, |
| w, h, config, |
| activate, this.graphicActions, this.currentGraphicOptions); |
| } |
| return; |
| } |
| throw new RjException("Invalid GD devId: " + devId); |
| } |
| |
| private void removeGraphic(final int devId) { |
| if (devId >= 0 && devId < this.graphics.length) { |
| if (this.graphics[devId] != null) { |
| try { |
| this.graphicFactory.closeGraphic(this.graphics[devId]); |
| } |
| catch (final Exception e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, |
| "An error occurred when closing R graphic (Device " + (devId+1) + ").", e )); |
| } |
| this.graphics[devId]= null; |
| } |
| } |
| } |
| |
| protected RClientGraphic getGraphic(final int devId) { |
| if (devId >= 0 && devId < this.graphics.length) { |
| final RClientGraphic graphic= this.graphics[devId]; |
| if (graphic != null) { |
| return graphic; |
| } |
| } |
| return this.graphicDummy; |
| } |
| |
| public void disposeAllGraphics() { |
| for (int devId= 0; devId < this.graphics.length; devId++) { |
| removeGraphic(devId); |
| } |
| } |
| |
| |
| public final void activateConsole() { |
| if (this.rjConsoleEngine == null) { |
| throw new IllegalStateException("Missing REngine"); |
| } |
| this.consoleReadCallbackRequired= true; |
| } |
| |
| public final void answerConsole(final String input, final IProgressMonitor monitor) throws CoreException { |
| this.consoleReadCallback.setAnswer(input); |
| runMainLoop(null, this.consoleReadCallback, monitor); |
| |
| this.runFinishTask= false; |
| } |
| |
| public final boolean isConsoleReady() { |
| return (this.consoleReadCallback != null); |
| } |
| |
| |
| private Map<String, Object> getServerData() throws RemoteException { |
| if (this.platformData != null) { |
| return this.platformData; |
| } |
| final Map<String, Object> data= this.rjConsoleEngine.getPlatformData(); |
| if (data != null && data.containsKey("version.string")) { //$NON-NLS-1$ |
| this.platformData= data; |
| } |
| return data; |
| } |
| |
| public final RPlatform getRPlatform() { |
| synchronized (this.platformLock) { |
| if (this.platformObj == null) { |
| try { |
| final Map<String, Object> data= getServerData(); |
| if (data != null && data.containsKey("version.string")) { |
| this.platformObj= new RPlatform((String) this.platformData.get("os.type"), |
| (String) this.platformData.get("file.sep"), |
| (String) this.platformData.get("path.sep"), |
| (String) this.platformData.get("version.string"), |
| (String) this.platformData.get("os.name"), |
| (String) this.platformData.get("os.arch"), |
| (String) this.platformData.get("os.version") ); |
| } |
| } |
| catch (final RemoteException e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, |
| "An error occured when loading data for RPlatform information.", e)); |
| } |
| } |
| return this.platformObj; |
| } |
| } |
| |
| public final String getProperty(final String key) { |
| synchronized (this.platformLock) { |
| try { |
| final Map<String, Object> data= getServerData(); |
| if (data != null) { |
| final Object value= data.get(key); |
| return (value instanceof String) ? (String) value : null; |
| } |
| } |
| catch (final RemoteException e) { |
| } |
| return null; |
| } |
| } |
| |
| |
| public final void finishTask( |
| final IProgressMonitor monitor) throws CoreException { |
| if (!this.runFinishTask) { |
| return; |
| } |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new MainCtrlCmdItem(MainCtrlCmdItem.OP_FINISH_TASK, |
| 0 )), monitor); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return; |
| } |
| finally { |
| finalizeDataLevel(); |
| this.runFinishTask= false; |
| } |
| } |
| |
| |
| public boolean isValidEnvir(final RObject envir) { |
| switch (envir.getRObjectType()) { |
| case RObject.TYPE_REFERENCE: |
| return (((RReference) envir).getReferencedRObjectType() == RObject.TYPE_ENVIRONMENT); |
| case RObject.TYPE_LANGUAGE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| public final void evalVoid(final String expression, final RObject envir, |
| final IProgressMonitor monitor) throws CoreException { |
| if (expression == null) { |
| throw new NullPointerException("expression"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.EVAL_EXPR_VOID, |
| 0, expression, null, null, envir )), monitor); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return; |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public final void evalVoid(final String name, final RList args, final RObject envir, |
| final IProgressMonitor monitor) throws CoreException { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| if (args == null) { |
| throw new NullPointerException("args"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.EVAL_FCALL_VOID, |
| 0, name, args, null, envir )), monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return; |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public RObject evalData(final String expression, final RObject envir, |
| final String factoryId, final int options, final int depth, |
| final IProgressMonitor monitor) throws CoreException { |
| if (expression == null) { |
| throw new NullPointerException("expression"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final byte checkedDepth= (depth < Byte.MAX_VALUE) ? (byte) depth : Byte.MAX_VALUE; |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.EVAL_EXPR_DATA, |
| options, checkedDepth, expression, null, null, envir, factoryId )), monitor); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((DataCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public RObject evalData(final String name, final RObject args, final RObject envir, |
| final String factoryId, final int options, final int depth, |
| final IProgressMonitor monitor) throws CoreException { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| if (args == null) { |
| throw new NullPointerException("args"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final byte checkedDepth= (depth < Byte.MAX_VALUE) ? (byte) depth : Byte.MAX_VALUE; |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.EVAL_FCALL_DATA, |
| options, checkedDepth, name, args, null, envir, factoryId )), monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((DataCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public final RObject evalData(final RReference reference, |
| final String factoryId, final int options, final int depth, |
| final IProgressMonitor monitor) throws CoreException { |
| final byte checkedDepth= (depth < Byte.MAX_VALUE) ? (byte) depth : Byte.MAX_VALUE; |
| final int level= newDataLevel(); |
| try { |
| final long handle= reference.getHandle(); |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.RESOLVE_DATA, |
| options, checkedDepth, Long.toString(handle), null, null, null, factoryId )), monitor ); |
| |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((DataCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public RObject evalData(final byte envType, final String name, |
| final String factoryId, final int options, final int depth, |
| final IProgressMonitor monitor) throws CoreException { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| final Operation operation; |
| switch (envType) { |
| case REnvironment.ENVTYPE_NAMESPACE: |
| operation= DataCmdItem.EVAL_NAMESPACE_DATA; |
| break; |
| case REnvironment.ENVTYPE_NAMESPACE_EXPORTS: |
| operation= DataCmdItem.EVAL_NAMESPACE_EXPORTS_DATA; |
| break; |
| default: |
| throw new IllegalArgumentException("envType= " + envType); |
| } |
| |
| final byte checkedDepth= (depth < Byte.MAX_VALUE) ? (byte) depth : Byte.MAX_VALUE; |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(operation, |
| options, checkedDepth, |
| name, null, null, null, factoryId )), monitor ); |
| |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((DataCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public final void assignData(final String expression, final RObject data, final RObject envir, |
| final IProgressMonitor monitor) throws CoreException { |
| if (expression == null) { |
| throw new NullPointerException("expression"); |
| } |
| if (data == null) { |
| throw new NullPointerException("data"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.ASSIGN_DATA, |
| 0, null, data, expression, envir )), monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Assignment failed: " + status.getMessage(), null)); |
| } |
| } |
| return; |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public final void assignData(final String name, final RObject args, final String expression, |
| final RObject envir, |
| final IProgressMonitor monitor) throws CoreException { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| if (args == null) { |
| throw new NullPointerException("args"); |
| } |
| if (expression == null) { |
| throw new NullPointerException("expression"); |
| } |
| if (envir != null && !isValidEnvir(envir)) { |
| throw new IllegalArgumentException("envir"); |
| } |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.ASSIGN_FCALL, |
| 0, name, args, expression, envir )), monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Assignment failed: " + status.getMessage(), null)); |
| } |
| } |
| return; |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public RObject[] findData(final String symbol, final RObject envir, final boolean inherits, |
| final String factoryId, final int options, final int depth, |
| final IProgressMonitor monitor) throws CoreException { |
| if (symbol == null) { |
| throw new NullPointerException("symbol"); |
| } |
| final byte checkedDepth= (depth < Byte.MAX_VALUE) ? (byte) depth : Byte.MAX_VALUE; |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new DataCmdItem(DataCmdItem.FIND_DATA, |
| (inherits) ? (options | 0x1000) : options, checkedDepth, |
| symbol, null, null, envir, factoryId )), monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Evaluation failed: " + status.getMessage(), null)); |
| } |
| } |
| final DataCmdItem dataItem= (DataCmdItem) this.dataAnswer[level]; |
| return (dataItem.getRho() != null) ? |
| new RObject[] { dataItem.getData(), dataItem.getRho() } : |
| null; |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| |
| public void downloadFile(final OutputStream out, final String fileName, final int options, final IProgressMonitor monitor) throws CoreException { |
| final BinExchange request= new BinExchange(out, fileName, this.rjConsoleEngine, options); |
| final BinExchange answer; |
| try { |
| answer= (BinExchange) runAsync(request); |
| } |
| finally { |
| request.clear(); |
| } |
| if (answer == null || !answer.isOK()) { |
| final RjsStatus status= (answer != null) ? answer.getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Downloading file failed: " + status.getMessage(), null)); |
| } |
| } |
| } |
| |
| public byte[] downloadFile(final String fileName, final int options, final IProgressMonitor monitor) throws CoreException { |
| final BinExchange request= new BinExchange(fileName, this.rjConsoleEngine, options); |
| final BinExchange answer; |
| try { |
| answer= (BinExchange) runAsync(request); |
| } |
| finally { |
| request.clear(); |
| } |
| if (answer == null || !answer.isOK()) { |
| final RjsStatus status= (answer != null) ? answer.getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Downloading file failed: " + status.getMessage(), null)); |
| } |
| } |
| return answer.getBytes(); |
| } |
| |
| public void uploadFile(final InputStream in, final long length, final String fileName, final int options, final IProgressMonitor monitor) throws CoreException { |
| final BinExchange request= new BinExchange(in, length, fileName, this.rjConsoleEngine, options); |
| final BinExchange answer; |
| try { |
| answer= (BinExchange) runAsync(request); |
| } |
| finally { |
| request.clear(); |
| } |
| if (answer == null || !answer.isOK()) { |
| final RjsStatus status= (answer != null) ? answer.getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Uploading file failed: " + status.getMessage(), null)); |
| } |
| } |
| } |
| |
| |
| public Object execSyncDbgOp(final byte dbgOp, final RJIOExternalizable request, |
| final IProgressMonitor monitor) throws CoreException { |
| if (this.dbgOpRequest) { |
| throw new IllegalStateException(); |
| } |
| this.dbgOpRequest= true; |
| try { |
| runMainLoop(null, new DbgCmdItem(dbgOp, 0, request), monitor); |
| if (this.dbgOpAnswer == null || !this.dbgOpAnswer.isOK()) { |
| final RjsStatus status= (this.dbgOpAnswer != null) ? this.dbgOpAnswer.getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Dbg operation failed: " + status.getMessage(), null)); |
| } |
| } |
| { final Object data= this.dbgOpAnswer.getData(); |
| if (data instanceof CtrlReport) { |
| final CtrlReport report= (CtrlReport) data; |
| if (!report.isEngineSuspended()) { |
| this.consoleReadCallback= null; |
| } |
| } |
| return data; |
| } |
| } |
| finally { |
| this.dbgOpRequest= false; |
| this.dbgOpAnswer= null; |
| } |
| } |
| |
| public void execAsyncDbgOp(final byte op, final RJIOExternalizable request) |
| throws CoreException { |
| execAsync(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| runAsync(new DbgCmdItem(op, 0, request)); |
| // in future check returned status if required |
| } |
| catch (final CoreException e) { |
| log(new Status(IStatus.ERROR, RJ_CLIENT_ID, -1, |
| "An error occurred when executing background dbg operation.", e)); |
| } |
| } |
| }); |
| } |
| |
| |
| public int getGraphicOptions() { |
| return this.currentGraphicOptions; |
| } |
| |
| public void setGraphicOptions(final int options) { |
| this.currentGraphicOptions= options; |
| this.lastGraphic= null; |
| } |
| |
| public RClientGraphic getLastGraphic() { |
| return this.lastGraphic; |
| } |
| |
| public Object execSyncGraphicOp(final int devId, final byte op, |
| final IProgressMonitor monitor) throws CoreException { |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new GraOpCmdItem(devId, op)), |
| monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Graphics operation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((GraOpCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| public Object execSyncGraphicOp(final int devId, final byte op, final RJIOExternalizable data, |
| final IProgressMonitor monitor) throws CoreException { |
| final int level= newDataLevel(); |
| try { |
| runMainLoop(null, createDataRequestId(level, new GraOpCmdItem(devId, op, data)), |
| monitor ); |
| if (this.dataAnswer[level] == null || !this.dataAnswer[level].isOK()) { |
| final RjsStatus status= (this.dataAnswer[level] != null) ? this.dataAnswer[level].getStatus() : MISSING_ANSWER_STATUS; |
| if (status.getSeverity() == RjsStatus.CANCEL) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| else { |
| throw new CoreException(new Status(status.getSeverity(), RJ_CLIENT_ID, status.getCode(), |
| "Graphics operation failed: " + status.getMessage(), null)); |
| } |
| } |
| return ((GraOpCmdItem) this.dataAnswer[level]).getData(); |
| } |
| finally { |
| finalizeDataLevel(); |
| } |
| } |
| |
| |
| public void addCancelHandler(final Callable<Boolean> handler) { |
| synchronized (this.cancelHandler) { |
| this.cancelHandler.add(handler); |
| } |
| } |
| |
| public void removeCancelHandler(final Callable<Boolean> handler) { |
| synchronized (this.cancelHandler) { |
| final int idx= this.cancelHandler.lastIndexOf(handler); |
| if (idx >= 0) { |
| this.cancelHandler.remove(idx); |
| } |
| } |
| } |
| |
| protected Callable<Boolean>[] getCancelHandlers() { |
| synchronized (this.cancelHandler) { |
| return this.cancelHandler.toArray(new Callable[this.cancelHandler.size()]); |
| } |
| } |
| |
| public Lock getWaitLock() { |
| return this.clientWaitLock; |
| } |
| |
| public void waitingForUser() { |
| if (this.hotModeRequested.get()) { |
| this.clientWaitLock.unlock(); |
| try { |
| startHotMode(); |
| } |
| finally { |
| this.clientWaitLock.lock(); |
| } |
| return; |
| } |
| |
| try { |
| this.clientWaitCondition.awaitNanos(1000 * 100); |
| } |
| catch (final InterruptedException e1) { |
| } |
| } |
| |
| public void resume() { |
| this.clientWaitCondition.signal(); |
| } |
| |
| } |