blob: 4fe855b91e2fec7715d66eda5a686b5722548283 [file] [log] [blame]
/*=============================================================================#
# 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();
}
}