/*=============================================================================#
# Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the GNU Lesser General Public License
# v2.1 which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/lgpl.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.rj.server.jri;
import static de.walware.rj.server.RjsComObject.V_ERROR;
import static de.walware.rj.server.RjsComObject.V_FALSE;
import static de.walware.rj.server.RjsComObject.V_OK;
import static de.walware.rj.server.RjsComObject.V_TRUE;
import static de.walware.rj.server.Server.S_CONNECTED;
import static de.walware.rj.server.Server.S_DISCONNECTED;
import static de.walware.rj.server.Server.S_NOT_STARTED;
import static de.walware.rj.server.Server.S_STOPPED;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_CTRL_COMMON;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_CTRL_REQUEST_CANCEL;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_CTRL_REQUEST_HOT_MODE;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_DATA_ASSIGN_DATA;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_DATA_COMMON;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_DATA_EVAL_DATA;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_DBG_COMMON;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_SRV_COMMON;
import static de.walware.rj.server.jri.JRIServerErrors.CODE_SRV_EVAL_DATA;
import static de.walware.rj.server.jri.JRIServerErrors.LOGGER;
import static de.walware.rj.server.jri.JRIServerRni.EVAL_MODE_DEFAULT;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import org.rosuda.JRI.RConfig;
import org.rosuda.JRI.RMainLoopCallbacks;
import org.rosuda.JRI.Rengine;
import de.walware.rj.RjException;
import de.walware.rj.RjInitFailedException;
import de.walware.rj.data.RDataUtil;
import de.walware.rj.data.RList;
import de.walware.rj.data.RObject;
import de.walware.rj.data.RObjectFactory;
import de.walware.rj.data.RStore;
import de.walware.rj.data.defaultImpl.RMissing;
import de.walware.rj.server.ConsoleEngine;
import de.walware.rj.server.ConsoleMessageCmdItem;
import de.walware.rj.server.ConsoleReadCmdItem;
import de.walware.rj.server.ConsoleWriteCmdItem;
import de.walware.rj.server.CtrlCmdItem;
import de.walware.rj.server.DataCmdItem;
import de.walware.rj.server.DataCmdItem.Operation;
import de.walware.rj.server.DbgCmdItem;
import de.walware.rj.server.ExtUICmdItem;
import de.walware.rj.server.GraOpCmdItem;
import de.walware.rj.server.MainCmdC2SList;
import de.walware.rj.server.MainCmdItem;
import de.walware.rj.server.MainCmdS2CList;
import de.walware.rj.server.MainCtrlCmdItem;
import de.walware.rj.server.RJ;
import de.walware.rj.server.RjsComConfig;
import de.walware.rj.server.RjsComObject;
import de.walware.rj.server.RjsException;
import de.walware.rj.server.RjsStatus;
import de.walware.rj.server.Server;
import de.walware.rj.server.dbg.DbgEnablement;
import de.walware.rj.server.dbg.DbgFilterState;
import de.walware.rj.server.dbg.DbgListener;
import de.walware.rj.server.dbg.DbgRequest;
import de.walware.rj.server.dbg.ElementTracepointInstallationRequest;
import de.walware.rj.server.dbg.FlagTracepointInstallationRequest;
import de.walware.rj.server.dbg.FrameContextDetailRequest;
import de.walware.rj.server.dbg.SetDebugRequest;
import de.walware.rj.server.dbg.TracepointEvent;
import de.walware.rj.server.dbg.TracepointStatesUpdate;
import de.walware.rj.server.gr.Coord;
import de.walware.rj.server.gr.GraOp;
import de.walware.rj.server.gr.RjsGraphicManager;
import de.walware.rj.server.srvImpl.AbstractServerControl;
import de.walware.rj.server.srvImpl.ConsoleEngineImpl;
import de.walware.rj.server.srvImpl.DefaultServerImpl;
import de.walware.rj.server.srvImpl.InternalEngine;
import de.walware.rj.server.srvImpl.RJClassLoader;
import de.walware.rj.server.srvext.Client;
import de.walware.rj.server.srvext.ExtServer;
import de.walware.rj.server.srvext.ServerRuntimePlugin;
import de.walware.rj.server.srvext.ServerUtil;
/**
* Remove server based on
*/
public final class JRIServer extends RJ
implements InternalEngine, RMainLoopCallbacks, ExtServer, DbgListener {
private static final int[] VERSION = new int[] { 2, 1, 0 };
private static final int ENGINE_NOT_STARTED = 0;
private static final int ENGINE_RUN_IN_R = 1;
private static final int ENGINE_WAIT_FOR_CLIENT = 2;
private static final int ENGINE_STOPPED = 4;
private static final int CLIENT_NONE = 0;
private static final int CLIENT_OK = 1;
private static final int CLIENT_OK_WAIT = 2;
private static final int CLIENT_CANCEL = 3;
private static final long STALE_SPAN = 5L * 60L * 1000000000L;
private static final int KILO = 1024;
private static final int MEGA = 1024 * KILO;
private static final int GIGA = 1024 * MEGA;
private static final long REQUIRED_JRI_API = 0x010a;
private static long s2long(final String s, final long defaultValue) {
if (s != null && s.length() > 0) {
final int multi;
switch (s.charAt(s.length()-1)) {
case 'G':
multi = GIGA;
break;
case 'M':
multi = MEGA;
break;
case 'K':
multi = KILO;
break;
case 'k':
multi = 1000;
break;
default:
multi = 1;
break;
}
try {
if (multi != 1) {
return Long.parseLong(s.substring(0, s.length()-1)) * multi;
}
else {
return Long.parseLong(s);
}
}
catch (final NumberFormatException e) {}
}
return defaultValue;
}
private class InitCallbacks implements RMainLoopCallbacks {
@Override
public String rReadConsole(final Rengine re, final String prompt, final int addToHistory) {
initEngine(re);
return JRIServer.this.rReadConsole(re, prompt, addToHistory);
}
@Override
public void rWriteConsole(final Rengine re, final String text, final int oType) {
initEngine(re);
JRIServer.this.rWriteConsole(re, text, oType);
}
@Override
public void rFlushConsole(final Rengine re) {
initEngine(re);
JRIServer.this.rFlushConsole(re);
}
@Override
public void rBusy(final Rengine re, final int which) {
initEngine(re);
JRIServer.this.rBusy(re, which);
}
@Override
public void rShowMessage(final Rengine re, final String message) {
initEngine(re);
JRIServer.this.rShowMessage(re, message);
}
@Override
public String rChooseFile(final Rengine re, final int newFile) {
initEngine(re);
return JRIServer.this.rChooseFile(re, newFile);
}
@Override
public void rLoadHistory(final Rengine re, final String filename) {
initEngine(re);
JRIServer.this.rLoadHistory(re, filename);
}
@Override
public void rSaveHistory(final Rengine re, final String filename) {
initEngine(re);
JRIServer.this.rSaveHistory(re, filename);
}
@Override
public long rExecJCommand(final Rengine re, final String commandId, final long argsExpr, final int options) {
initEngine(re);
return JRIServer.this.rExecJCommand(re, commandId, argsExpr, options);
}
@Override
public void rProcessJEvents(final Rengine re) {
JRIServer.this.mainExchangeLock.lock();
try {
if (JRIServer.this.hotModeRequested) {
JRIServer.this.hotModeDelayed = true;
}
}
finally {
JRIServer.this.mainExchangeLock.unlock();
}
}
}
private class HotLoopCallbacks implements RMainLoopCallbacks {
@Override
public String rReadConsole(final Rengine re, final String prompt, final int addToHistory) {
if (prompt.startsWith("Browse")) {
return "c\n";
}
return "\n";
}
@Override
public void rWriteConsole(final Rengine re, final String text, final int oType) {
JRIServer.this.rWriteConsole(re, text, oType);
}
@Override
public void rFlushConsole(final Rengine re) {
JRIServer.this.rFlushConsole(re);
}
@Override
public void rBusy(final Rengine re, final int which) {
JRIServer.this.rBusy(re, which);
}
@Override
public void rShowMessage(final Rengine re, final String message) {
JRIServer.this.rShowMessage(re, message);
}
@Override
public String rChooseFile(final Rengine re, final int newFile) {
return null;
}
@Override
public void rLoadHistory(final Rengine re, final String filename) {
}
@Override
public void rSaveHistory(final Rengine re, final String filename) {
}
@Override
public long rExecJCommand(final Rengine re, final String commandId, final long argsExpr, final int options) {
return 0;
}
@Override
public void rProcessJEvents(final Rengine re) {
}
}
private AbstractServerControl control;
private Server publicServer;
private RJClassLoader rClassLoader;
private Rengine rEngine;
private List<String> rArgs;
private final RConfig rConfig;
private long rMemSize;
final ReentrantLock mainExchangeLock = new ReentrantLock();
private final Condition mainExchangeClient = this.mainExchangeLock.newCondition();
private final Condition mainExchangeR = this.mainExchangeLock.newCondition();
private final ReentrantLock mainInterruptLock = new ReentrantLock();
private int mainLoopState;
private boolean mainLoopBusyAtServer = false;
private boolean mainLoopBusyAtClient = true;
private int mainLoopClient0State;
private int mainLoopClientListen;
private int mainLoopServerStack;
private final JRIServerIOStreams ioStreams= new JRIServerIOStreams(this);
private final MainCmdItem[] mainLoopS2CNextCommandsFirst = new MainCmdItem[2];
private final MainCmdItem[] mainLoopS2CNextCommandsLast = new MainCmdItem[2];
private final MainCmdS2CList[] mainLoopS2CLastCommands = new MainCmdS2CList[] { new MainCmdS2CList(), new MainCmdS2CList() };
private final List<MainCmdItem> mainLoopS2CRequest = new ArrayList<>();
private MainCmdItem mainLoopC2SCommandFirst;
private int mainLoopS2CAnswerFail;
private ConsoleReadCmdItem mainLoopPrompt;
private boolean safeMode;
private boolean hotModeRequested;
private boolean hotModeDelayed;
private boolean hotMode;
private final RMainLoopCallbacks hotModeCallbacks = new HotLoopCallbacks();
private int serverState;
private final ReentrantReadWriteLock[] clientLocks = new ReentrantReadWriteLock[] {
new ReentrantReadWriteLock(), new ReentrantReadWriteLock() };
private Client client0;
private ConsoleEngine client0Engine;
private ConsoleEngine client0ExpRef;
private ConsoleEngine client0PrevExpRef;
private volatile long client0LastPing;
private final Object pluginsLock = new Object();
private ServerRuntimePlugin[] pluginsList = new ServerRuntimePlugin[0];
private final Map<String, Object> platformDataValues = new HashMap<>();
private final ServerUtils utils = new ServerUtils(this.platformDataValues);
private RObjectFactory rObjectFactory;
private JRIServerGraphics graphics;
private int rniListsMaxLength = 10000;
private int rniEnvsMaxLength = 10000;
private JRIServerRni rni;
private JRIServerDbg dbg;
public JRIServer() {
this.rConfig = new RConfig();
// default 16M, overwritten by arg --max-cssize, if set
this.rConfig.MainCStack_Size = s2long(
System.getProperty("de.walware.rj.rMainCStack_Size"), 64 * MEGA );
// default true
this.rConfig.MainCStack_SetLimit = !"false".equalsIgnoreCase(
System.getProperty("de.walware.rj.rMainCStack_SetLimit") );
this.rMemSize= s2long(System.getenv("R_MAX_MEM_SIZE"), 0);
this.mainLoopState = ENGINE_NOT_STARTED;
this.mainLoopClient0State = CLIENT_NONE;
this.serverState = S_NOT_STARTED;
this.platformDataValues.put("os.name", System.getProperty("os.name"));
this.platformDataValues.put("os.arch", System.getProperty("os.arch"));
this.platformDataValues.put("os.version", System.getProperty("os.version"));
this.platformDataValues.put("file.separator", System.getProperty("file.separator"));
this.platformDataValues.put("path.separator", System.getProperty("path.separator"));
this.platformDataValues.put("line.separator", System.getProperty("line.separator"));
}
@Override
public int[] getVersion() {
return VERSION;
}
@Override
public void init(final AbstractServerControl control, final Server publicServer, final RJClassLoader loader) throws Exception {
if (loader == null) {
throw new NullPointerException("loader");
}
this.control = control;
this.publicServer = publicServer;
this.rClassLoader = loader;
}
@Override
public void addPlugin(final ServerRuntimePlugin plugin) {
if (plugin == null) {
throw new IllegalArgumentException();
}
synchronized (this.pluginsLock) {
final int oldSize = this.pluginsList.length;
final ServerRuntimePlugin[] newList = new ServerRuntimePlugin[oldSize + 1];
System.arraycopy(this.pluginsList, 0, newList, 0, oldSize);
newList[oldSize] = plugin;
this.pluginsList= newList;
}
}
@Override
public void removePlugin(final ServerRuntimePlugin plugin) {
if (plugin == null) {
return;
}
synchronized (this.pluginsLock) {
final int oldSize = this.pluginsList.length;
for (int i = 0; i < oldSize; i++) {
if (this.pluginsList[i] == plugin) {
final ServerRuntimePlugin[] newList = new ServerRuntimePlugin[oldSize - 1];
System.arraycopy(this.pluginsList, 0, newList, 0, i);
System.arraycopy(this.pluginsList, i + 1, newList, i, oldSize - i - 1);
this.pluginsList = newList;
return;
}
}
}
}
private void handlePluginError(final ServerRuntimePlugin plugin, final Throwable error) {
// log and disable
LOGGER.log(Level.SEVERE, "[Plugins] An error occurred in plug-in '"+plugin.getSymbolicName()+"', plug-in will be disabled.", error);
removePlugin(plugin);
try {
plugin.rjStop(V_ERROR);
}
catch (final Throwable stopError) {
LOGGER.log(Level.WARNING, "[Plugins] An error occurred when trying to disable plug-in '"+plugin.getSymbolicName()+"'.", error);
}
}
private void connectClient0(final Client client,
final ConsoleEngine consoleEngine, final ConsoleEngine export) {
disconnectClient0();
this.client0 = client;
this.client0Engine = consoleEngine;
this.client0ExpRef = export;
DefaultServerImpl.addClient(export);
this.client0LastPing = System.nanoTime();
this.serverState = S_CONNECTED;
}
private void disconnectClient0() {
if (this.serverState >= S_CONNECTED && this.serverState < S_STOPPED) {
this.serverState = S_DISCONNECTED;
}
if (this.client0PrevExpRef != null) {
try {
UnicastRemoteObject.unexportObject(this.client0PrevExpRef, true);
}
catch (final Exception e) {}
this.client0PrevExpRef = null;
}
if (this.client0 != null) {
final Client client = this.client0;
DefaultServerImpl.removeClient(this.client0ExpRef);
this.client0 = null;
this.client0Engine = null;
this.client0PrevExpRef = this.client0ExpRef;
this.client0ExpRef = null;
if (this.hotModeRequested) {
this.hotModeRequested = false;
}
internalClearClient(client);
}
}
private void checkClient(final Client client) throws RemoteException {
// final String expectedClient = this.currentMainClientId;
// final String remoteClient;
// try {
// remoteClient = RemoteServer.getClientHost();
// }
// catch (final ServerNotActiveException e) {
// throw new IllegalStateException(e);
// }
if (client.slot == 0 && this.client0 != client
// || expectedClient == null
// || !expectedClient.equals(remoteClient)
) {
throw new ConnectException("Not connected.");
}
}
@Override
public Client getCurrentClient() {
return this.client0;
}
@Override
public int getState() {
final int state = this.serverState;
if (state == Server.S_CONNECTED
&& (System.nanoTime() - this.client0LastPing) > STALE_SPAN) {
return Server.S_CONNECTED_STALE;
}
return state;
}
@Override
public synchronized ConsoleEngine start(final Client client, final Map<String, ? extends Object> properties) throws RemoteException {
assert (client.slot == 0);
this.clientLocks[client.slot].writeLock().lock();
try {
if (this.mainLoopState != ENGINE_NOT_STARTED) {
throw new IllegalStateException("R engine is already started.");
}
final ConsoleEngineImpl consoleEngine = new ConsoleEngineImpl(this.publicServer, this, client);
final ConsoleEngine export = (ConsoleEngine) this.control.exportObject(consoleEngine);
final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.rClassLoader);
if (Rengine.getVersion() < REQUIRED_JRI_API) {
final String message = "Unsupported JRI version (API found: 0x" + Long.toHexString(Rengine.getVersion()) + ", required: 0x" + Long.toHexString(REQUIRED_JRI_API) + ").";
LOGGER.log(Level.SEVERE, message);
internalRStopped();
throw new RjInitFailedException(message);
}
final String[] args = checkArgs((String[]) properties.get("args"));
if (LOGGER.isLoggable(Level.CONFIG)) {
final StringBuilder sb = new StringBuilder("R arguments:");
ServerUtil.prettyPrint(Arrays.asList(args), sb);
LOGGER.log(Level.CONFIG, sb.toString());
}
// this.ioStreams.init();
this.mainLoopState = ENGINE_RUN_IN_R;
this.hotMode = true;
final Rengine re = new Rengine(args, this.rConfig, true, new InitCallbacks());
while (this.rEngine != re) {
Thread.sleep(100);
}
if (!re.waitForR()) {
internalRStopped();
throw new IllegalThreadStateException("R thread not started (" + re.getExitCode() + ")");
}
this.mainExchangeLock.lock();
try {
this.mainLoopS2CAnswerFail = 0;
this.mainLoopClient0State = CLIENT_OK;
}
finally {
this.mainExchangeLock.unlock();
}
setProperties(client.slot, properties, true);
LOGGER.log(Level.INFO, "R engine started successfully. New Client-State: 'Connected'.");
connectClient0(client, consoleEngine, export);
return export;
}
catch (final Throwable e) {
this.serverState = S_STOPPED;
final String message = "Could not start the R engine";
LOGGER.log(Level.SEVERE, message, e);
if (export != null) {
UnicastRemoteObject.unexportObject(export, true);
}
throw new RemoteException(message, e);
}
finally {
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
finally {
this.clientLocks[client.slot].writeLock().unlock();
}
}
@Override
public void setProperties(final Client client, final Map<String, ? extends Object> properties) throws RemoteException {
this.clientLocks[client.slot].readLock().lock();
try {
checkClient(client);
setProperties(client.slot, properties, properties.containsKey("rj.com.init"));
}
finally {
this.clientLocks[client.slot].readLock().unlock();
}
}
private void setProperties(final byte slot, final Map<String, ? extends Object> properties, final boolean init) {
{ final Object max = properties.get(RjsComConfig.RJ_DATA_STRUCTS_LISTS_MAX_LENGTH_PROPERTY_ID);
if (max instanceof Integer) {
this.rniListsMaxLength = ((Integer) max).intValue();
}
}
{ final Object max = properties.get(RjsComConfig.RJ_DATA_STRUCTS_ENVS_MAX_LENGTH_PROPERTY_ID);
if (max instanceof Integer) {
this.rniEnvsMaxLength = ((Integer) max).intValue();
}
}
if (init) {
final Object id = properties.get(RjsComConfig.RJ_COM_S2C_ID_PROPERTY_ID);
if (id instanceof Integer) {
this.mainLoopS2CLastCommands[slot].setId(((Integer) id).intValue());
}
else {
this.mainLoopS2CLastCommands[slot].setId(0);
}
}
else {
properties.remove(RjsComConfig.RJ_COM_S2C_ID_PROPERTY_ID);
}
super.setClientProperties(slot, properties);
}
private void initEngine(final Rengine re) {
this.rEngine = re;
this.rEngine.setContextClassLoader(this.rClassLoader);
this.rObjectFactory = new JRIObjectFactory();
RjsComConfig.setDefaultRObjectFactory(this.rObjectFactory);
this.rEngine.addMainLoopCallbacks(this.hotModeCallbacks);
try {
this.rni= new JRIServerRni(this.rEngine);
this.dbg= new JRIServerDbg(this, this.rni, this.utils);
loadPlatformData();
{ final String rNetworkEnvVar= System.getenv("R_NETWORK");
if (this.rClassLoader.getOSType() == RJClassLoader.OS_WIN
&& (this.rArgs.contains("--internet2")
|| "2".equals(rNetworkEnvVar)) ) {
if (this.utils.isRVersionLess(3, 3)) {
this.rEngine.rniEval(this.rEngine.rniParse("utils::setInternet2(use=TRUE)", 1),
this.rni.rniSafeBaseExecEnvP );
}
else {
this.rEngine.rniEval(this.rEngine.rniParse("base::options(download.file.method= 'wininet')", 1),
this.rni.rniSafeBaseExecEnvP );
}
}
else if ("1".equals(rNetworkEnvVar)) {
this.rEngine.rniEval(this.rEngine.rniParse("base::options(download.file.method= 'internal')", 1),
this.rni.rniSafeBaseExecEnvP );
}
}
if (this.rClassLoader.getOSType() == RJClassLoader.OS_WIN
&& this.rMemSize != 0) {
final long rniP = this.rEngine.rniEval(this.rEngine.rniParse("utils::memory.limit()", 1),
this.rni.rniSafeBaseExecEnvP );
if (rniP != 0) {
final long memSizeMB = this.rMemSize / MEGA;
final double[] array = this.rEngine.rniGetDoubleArray(rniP);
if (array != null && array.length == 1 && memSizeMB > array[0]) {
this.rEngine.rniEval(this.rEngine.rniParse("utils::memory.limit(size= "+memSizeMB+")", 1),
this.rni.rniSafeBaseExecEnvP );
}
}
}
this.ioStreams.init();
this.graphics = new JRIServerGraphics(this, this.rEngine, this.rni);
this.hotMode = false;
if (this.hotModeDelayed) {
this.hotModeRequested = true;
}
if (!this.hotModeRequested) {
return;
}
}
catch (final Throwable e) {
LOGGER.log(Level.SEVERE, "Failed to initialize engine.", e);
AbstractServerControl.exit(162);
throw new RuntimeException(); // for final fields
}
finally {
this.rEngine.addMainLoopCallbacks(JRIServer.this);
}
// if (this.hotModeRequested)
rProcessJEvents(this.rEngine);
}
private void loadPlatformData() {
final Map<String, String> platformDataCommands = new HashMap<>();
platformDataCommands.put("R:os.type", ".Platform$OS.type");
platformDataCommands.put("R:file.sep", ".Platform$file.sep");
platformDataCommands.put("R:path.sep", ".Platform$path.sep");
platformDataCommands.put("R.version.string", "paste(R.version$major, R.version$minor, sep=\".\")");
platformDataCommands.put("R:file.~", "path.expand(\"~\")");
try {
for (final Entry<String, String> entry : platformDataCommands.entrySet()) {
final DataCmdItem dataCmd= internalEvalData(new DataCmdItem(DataCmdItem.EVAL_EXPR_DATA,
0, (byte) 1, entry.getValue(), null, null, null, null ));
if (dataCmd != null && dataCmd.isOK()) {
final RObject data= dataCmd.getData();
if (data.getRObjectType() == RObject.TYPE_VECTOR) {
switch (data.getData().getStoreType()) {
case RStore.CHARACTER:
if (data.getLength() == 1) {
this.platformDataValues.put(entry.getKey(), data.getData().get(0));
this.platformDataValues.put(entry.getKey().substring(2), data.getData().get(0));
continue;
}
}
}
}
LOGGER.log(Level.WARNING, "The platform data item '" + entry.getKey() + "' could not be created.");
}
}
catch (final Throwable e) {
LOGGER.log(Level.SEVERE, "An error occurred when loading platform data.", e);
}
if (LOGGER.isLoggable(Level.FINE)) {
final StringBuilder sb= new StringBuilder("R Platform Data");
ServerUtil.prettyPrint(this.platformDataValues, sb);
LOGGER.log(Level.FINE, sb.toString());
}
}
@Override
public RjsGraphicManager getGraphicManager() {
return this.graphics;
}
@Override
public Map<String, Object> getPlatformData() {
return this.platformDataValues;
}
private String[] checkArgs(final String[] args) {
final List<String> checked = new ArrayList<>(args.length+1);
boolean saveState = false;
for (final String arg : args) {
if (arg != null && arg.length() > 0) {
// add other checks here
if (arg.equals("--interactive")) {
saveState = true;
}
else if (arg.startsWith("--max-cssize=")) {
long size = s2long(arg.substring(13), 0);
size = ((size + MEGA - 1) / MEGA) * MEGA;
if (size >= 4 * MEGA) {
this.rConfig.MainCStack_Size = size;
}
}
else if (arg.startsWith("--max-mem-size=")) {
final long size = s2long(arg.substring(15), 0);
if (size > 0) {
this.rMemSize = size;
}
}
checked.add(arg);
}
}
if (!saveState && this.rClassLoader.getOSType() != RJClassLoader.OS_WIN) {
checked.add(0, "--interactive");
}
this.rArgs = checked;
return checked.toArray(new String[checked.size()]);
}
@Override
public ConsoleEngine connect(final Client client, final Map<String, ? extends Object> properties) throws RemoteException {
assert (client.slot == 0);
this.clientLocks[client.slot].writeLock().lock();
try {
if (this.client0 == client) {
return this.client0ExpRef;
}
final ConsoleEngine consoleEngine = new ConsoleEngineImpl(this.publicServer, this, client);
final ConsoleEngine export = (ConsoleEngine) this.control.exportObject(consoleEngine);
this.mainExchangeLock.lock();
try {
this.mainLoopS2CAnswerFail = 0;
switch (this.mainLoopState) {
case ENGINE_WAIT_FOR_CLIENT:
case ENGINE_RUN_IN_R:
// exit old client
if (this.mainLoopClient0State == CLIENT_OK_WAIT) {
this.mainLoopClient0State = CLIENT_CANCEL;
this.mainExchangeClient.signalAll();
while (this.mainLoopClient0State == CLIENT_CANCEL) {
try {
this.mainExchangeClient.awaitNanos(100000000L);
}
catch (final InterruptedException e) {}
}
// setup new client
if (this.mainLoopClient0State != CLIENT_NONE) {
throw new AssertionError();
}
}
this.mainLoopBusyAtClient = true;
this.mainLoopClient0State = CLIENT_OK;
this.mainLoopS2CAnswerFail = 0;
if (this.mainLoopS2CLastCommands[0].getItems() != null) {
if (this.mainLoopS2CNextCommandsFirst[0] != null) {
MainCmdItem last = this.mainLoopS2CLastCommands[0].getItems();
while (last.next != null) {
last = last.next;
}
last.next = this.mainLoopS2CNextCommandsFirst[0];
}
this.mainLoopS2CNextCommandsFirst[0] = this.mainLoopS2CLastCommands[0].getItems();
}
if (this.mainLoopS2CNextCommandsFirst[0] == null && !this.mainLoopS2CRequest.isEmpty()) {
this.mainLoopS2CNextCommandsFirst[0] = this.mainLoopS2CNextCommandsLast[0] = this.mainLoopS2CRequest.get(this.mainLoopS2CRequest.size()-1);
}
// restore mainLoopS2CNextCommandsLast, if necessary
if (this.mainLoopS2CNextCommandsLast[0] == null && this.mainLoopS2CNextCommandsFirst != null) {
this.mainLoopS2CNextCommandsLast[0] = this.mainLoopS2CNextCommandsFirst[0];
while (this.mainLoopS2CNextCommandsLast[0].next != null) {
this.mainLoopS2CNextCommandsLast[0] = this.mainLoopS2CNextCommandsLast[0].next;
}
}
// notify listener client
// try {
// new RjsStatusImpl2(V_CANCEL, S_DISCONNECTED,
// (this.currentUsername != null) ? ("user "+ this.currentUsername) : null);
// }
// catch (Throwable e) {}
LOGGER.log(Level.INFO, "New client connected. New Client-State: 'Connected'.");
setProperties(client.slot, properties, true);
connectClient0(client, consoleEngine, export);
return export;
default:
throw new IllegalStateException("R engine is not running.");
}
}
catch (final Throwable e) {
final String message = "An error occurred when connecting.";
LOGGER.log(Level.SEVERE, message, e);
if (export != null) {
UnicastRemoteObject.unexportObject(export, true);
}
throw new RemoteException(message, e);
}
finally {
this.mainExchangeLock.unlock();
}
}
finally {
this.clientLocks[client.slot].writeLock().unlock();
}
}
@Override
public void disconnect(final Client client) throws RemoteException {
assert (client.slot == 0);
this.clientLocks[client.slot].writeLock().lock();
try {
checkClient(client);
this.mainExchangeLock.lock();
try {
if (this.mainLoopClient0State == CLIENT_OK_WAIT) {
// exit old client
this.mainLoopClient0State = CLIENT_CANCEL;
while (this.mainLoopClient0State == CLIENT_CANCEL) {
this.mainExchangeClient.signalAll();
try {
this.mainExchangeClient.awaitNanos(100000000L);
}
catch (final InterruptedException e) {}
}
// setup new client
if (this.mainLoopClient0State != CLIENT_NONE) {
throw new AssertionError();
}
}
else {
this.mainLoopClient0State = CLIENT_NONE;
}
disconnectClient0();
}
finally {
this.mainExchangeLock.unlock();
}
}
finally {
this.clientLocks[client.slot].writeLock().unlock();
}
}
@Override
public RjsComObject runMainLoop(final Client client, final RjsComObject command) throws RemoteException {
this.clientLocks[client.slot].readLock().lock();
boolean clientLock = true;
try {
checkClient(client);
this.mainLoopS2CLastCommands[client.slot].clear();
switch ((command != null) ? command.getComType() : RjsComObject.T_MAIN_LIST) {
case RjsComObject.T_PING:
return RjsStatus.OK_STATUS;
case RjsComObject.T_MAIN_LIST:
final MainCmdC2SList mainC2SCmdList = (MainCmdC2SList) command;
if (client.slot > 0 && mainC2SCmdList != null) {
MainCmdItem item = mainC2SCmdList.getItems();
while (item != null) {
item.slot = client.slot;
item = item.next;
}
}
this.mainExchangeLock.lock();
this.clientLocks[client.slot].readLock().unlock();
clientLock = false;
try {
return internalMainCallbackFromClient(client.slot, mainC2SCmdList);
}
finally {
this.mainExchangeLock.unlock();
}
case RjsComObject.T_CTRL:
return internalCtrl(client.slot, (CtrlCmdItem) command);
case RjsComObject.T_FILE_EXCHANGE:
return command;
default:
throw new IllegalArgumentException("Unknown command: " + "0x"+Integer.toHexString(command.getComType()) + ".");
}
}
finally {
if (clientLock) {
this.clientLocks[client.slot].readLock().unlock();
}
}
}
@Override
public RjsComObject runAsync(final Client client, final RjsComObject command) throws RemoteException {
this.clientLocks[client.slot].readLock().lock();
try {
checkClient(client);
if (command == null) {
throw new IllegalArgumentException("Missing command.");
}
switch (command.getComType()) {
case RjsComObject.T_PING:
if (client.slot == 0) {
this.client0LastPing = System.nanoTime();
}
return internalPing();
case RjsComObject.T_CTRL:
return internalCtrl(client.slot, (CtrlCmdItem) command);
case RjsComObject.T_DBG:
return internalAsyncDbg(client.slot, (DbgCmdItem) command);
case RjsComObject.T_FILE_EXCHANGE:
return command;
default:
throw new IllegalArgumentException("Unknown command: " + "0x"+Integer.toHexString(command.getComType()) + ".");
}
}
finally {
this.clientLocks[client.slot].readLock().unlock();
}
}
private RjsStatus internalCtrl(final byte slot, final CtrlCmdItem cmd) {
switch (cmd.getCtrlId()) {
case CtrlCmdItem.REQUEST_CANCEL:
try {
if (this.mainInterruptLock.tryLock(1L, TimeUnit.SECONDS)) {
try {
this.rni.rniInterrupted = true;
this.rEngine.rniStop(0);
if (this.dbg != null) {
this.dbg.rCancelled();
}
return RjsStatus.OK_STATUS;
}
catch (final Throwable e) {
LOGGER.log(Level.SEVERE, "An error occurred when trying to interrupt the R engine.", e);
return new RjsStatus(RjsStatus.ERROR, CODE_CTRL_REQUEST_CANCEL | 0x2);
}
finally {
this.mainInterruptLock.unlock();
}
}
}
catch (final InterruptedException e) {
Thread.interrupted();
}
return new RjsStatus(RjsStatus.ERROR, CODE_CTRL_REQUEST_CANCEL | 0x1, "Timeout.");
case CtrlCmdItem.REQUEST_HOT_MODE:
this.mainExchangeLock.lock();
try {
if (!this.hotModeRequested) {
if (this.hotMode) {
this.hotModeRequested = true;
}
else {
this.hotModeRequested = true;
this.rEngine.rniSetProcessJEvents(1);
this.mainExchangeR.signalAll();
}
}
return RjsStatus.OK_STATUS;
}
catch (final Exception e) {
LOGGER.log(Level.SEVERE, "An error occurred when requesting hot mode.", e);
return new RjsStatus(RjsStatus.ERROR, CODE_CTRL_REQUEST_HOT_MODE | 0x2);
}
finally {
this.mainExchangeLock.unlock();
}
}
return new RjsStatus(RjsStatus.ERROR, CODE_CTRL_COMMON | 0x2);
}
private RjsStatus internalPing() {
final Rengine r = this.rEngine;
if (r.isAlive()) {
return RjsStatus.OK_STATUS;
}
if (this.mainLoopState != ENGINE_STOPPED) {
// invalid state
}
return new RjsStatus(RjsStatus.WARNING, S_STOPPED);
}
private RjsComObject internalMainCallbackFromClient(final byte slot, final MainCmdC2SList mainC2SCmdList) {
// System.out.println("fromClient 1: " + mainC2SCmdList);
// System.out.println("C2S: " + this.mainLoopC2SCommandFirst);
// System.out.println("S2C: " + this.mainLoopS2CNextCommandsFirst[1]);
if (slot == 0 && this.mainLoopClient0State != CLIENT_OK) {
return new RjsStatus(RjsStatus.WARNING, S_DISCONNECTED);
}
if (this.mainLoopState == ENGINE_WAIT_FOR_CLIENT) {
if (mainC2SCmdList == null && this.mainLoopS2CNextCommandsFirst[slot] == null) {
if (slot == 0) {
if (this.mainLoopS2CAnswerFail < 3) { // retry
this.mainLoopS2CAnswerFail++;
this.mainLoopS2CNextCommandsFirst[0] = this.mainLoopS2CNextCommandsLast[0] = this.mainLoopS2CRequest.get(this.mainLoopS2CRequest.size()-1);
LOGGER.log(Level.WARNING, "Unanswered request - retry: " + this.mainLoopS2CNextCommandsLast[0]);
// continue ANSWER
}
else { // fail
this.mainLoopC2SCommandFirst = this.mainLoopS2CRequest.get(this.mainLoopS2CRequest.size()-1);
this.mainLoopC2SCommandFirst.setAnswer(new RjsStatus(RjsStatus.ERROR, 0));
this.mainLoopS2CNextCommandsFirst[0] = this.mainLoopS2CNextCommandsLast[0] = null;
LOGGER.log(Level.SEVERE, "Unanswered request - skip: " + this.mainLoopC2SCommandFirst);
// continue in R
}
}
else {
return new RjsStatus(RjsStatus.ERROR, RjsStatus.ERROR);
}
}
else { // ok
this.mainLoopS2CAnswerFail = 0;
}
}
if (mainC2SCmdList != null) {
domexAppend2S(mainC2SCmdList.getItems());
}
// System.out.println("fromClient 2: " + mainC2SCmdList);
// System.out.println("C2S: " + this.mainLoopC2SCommandFirst);
// System.out.println("S2C: " + this.mainLoopS2CNextCommandsFirst[1]);
this.mainExchangeR.signalAll();
if (slot == 0) {
this.mainLoopClient0State = CLIENT_OK_WAIT;
}
while (this.mainLoopS2CNextCommandsFirst[slot] == null
// && (this.mainLoopState != ENGINE_STOPPED)
&& (this.mainLoopState == ENGINE_RUN_IN_R
|| this.mainLoopC2SCommandFirst != null
|| this.hotModeRequested)
&& ((slot > 0)
|| ( (this.mainLoopClient0State == CLIENT_OK_WAIT)
&& (!this.ioStreams.domexHasOut())
&& (this.mainLoopBusyAtClient == this.mainLoopBusyAtServer) )
)) {
this.mainLoopClientListen++;
try {
this.mainExchangeClient.await(); // run in R
}
catch (final InterruptedException e) {}
finally {
this.mainLoopClientListen--;
}
}
if (slot == 0 && this.mainLoopClient0State == CLIENT_OK_WAIT) {
this.mainLoopClient0State = CLIENT_OK;
}
// System.out.println("fromClient 3: " + mainC2SCmdList);
// System.out.println("C2S: " + this.mainLoopC2SCommandFirst);
// System.out.println("S2C: " + this.mainLoopS2CNextCommandsFirst[1]);
// answer
if (slot > 0 || this.mainLoopClient0State == CLIENT_OK) {
this.ioStreams.domexSendOut();
if (this.mainLoopState == ENGINE_STOPPED && this.mainLoopS2CNextCommandsFirst[slot] == null) {
return new RjsStatus(RjsStatus.INFO, S_STOPPED);
}
this.mainLoopBusyAtClient = this.mainLoopBusyAtServer;
this.mainLoopS2CLastCommands[slot].setBusy(this.mainLoopBusyAtClient);
this.mainLoopS2CLastCommands[slot].setObjects(this.mainLoopS2CNextCommandsFirst[slot]);
this.mainLoopS2CNextCommandsFirst[slot] = null;
return this.mainLoopS2CLastCommands[slot];
}
else {
this.mainLoopClient0State = CLIENT_NONE;
return new RjsStatus(RjsStatus.CANCEL, S_DISCONNECTED);
}
}
private MainCmdItem internalMainFromR(final MainCmdItem initialItem) {
MainCmdItem item = initialItem;
boolean initial = true;
while (true) {
this.mainExchangeLock.lock();
try {
// System.out.println("fromR 1: " + item);
// System.out.println("C2S: " + this.mainLoopC2SCommandFirst);
// System.out.println("S2C: " + this.mainLoopS2CNextCommandsFirst[1]);
if (item != null) {
this.ioStreams.domexSendOut();
if (this.mainLoopS2CNextCommandsFirst[item.slot] == null) {
this.mainLoopS2CNextCommandsFirst[item.slot] = this.mainLoopS2CNextCommandsLast[item.slot] = item;
}
else {
this.mainLoopS2CNextCommandsLast[item.slot] = this.mainLoopS2CNextCommandsLast[item.slot].next = item;
}
}
this.mainExchangeClient.signalAll();
if (initial) {
if (initialItem == null || !initialItem.waitForClient()) {
return null;
}
initialItem.requestId = this.mainLoopS2CRequest.size();
this.mainLoopS2CRequest.add(initialItem);
initial = false;
}
// initial != null && initial.waitForClient()
if (this.mainLoopState == ENGINE_STOPPED) {
initialItem.setAnswer(new RjsStatus(RjsStatus.ERROR, S_STOPPED));
return initialItem;
}
if (this.mainLoopC2SCommandFirst == null) {
this.mainLoopState = ENGINE_WAIT_FOR_CLIENT;
final int stackId = ++this.mainLoopServerStack;
try {
if (Thread.currentThread() == this.rEngine) {
while ((this.mainLoopC2SCommandFirst == null && !this.hotModeRequested)
|| this.mainLoopServerStack > stackId ) {
int i = 0;
final ServerRuntimePlugin[] plugins = this.pluginsList;
this.mainExchangeLock.unlock();
this.mainInterruptLock.lock();
try {
for (; i < plugins.length; i++) {
plugins[i].rjIdle();
}
this.rEngine.rniIdle();
}
catch (final Throwable e) {
if (i < plugins.length) {
handlePluginError(plugins[i], e);
}
}
finally {
this.mainInterruptLock.unlock();
this.mainExchangeLock.lock();
}
if ((this.mainLoopC2SCommandFirst != null || this.hotModeRequested)
&& this.mainLoopServerStack <= stackId ) {
break;
}
try {
this.mainExchangeR.awaitNanos(50000000L);
}
catch (final InterruptedException e) {}
}
}
else {
// TODO log warning
while (this.mainLoopC2SCommandFirst == null || this.mainLoopServerStack > stackId) {
try {
this.mainExchangeR.awaitNanos(50000000L);
}
catch (final InterruptedException e) {}
}
}
}
finally {
this.mainLoopServerStack--;
this.mainLoopState = ENGINE_RUN_IN_R;
}
}
if (this.hotModeRequested) {
item = null;
}
else {
// initial != null && initial.waitForClient()
// && this.mainLoopC2SCommandFirst != null
item = this.mainLoopC2SCommandFirst;
this.mainLoopC2SCommandFirst = this.mainLoopC2SCommandFirst.next;
item.next = null;
// System.out.println("fromR 2: " + item);
// System.out.println("C2S: " + this.mainLoopC2SCommandFirst);
// System.out.println("S2C: " + this.mainLoopS2CNextCommandsFirst[1]);
if (item.getCmdType() < MainCmdItem.T_S2C_C2S) {
// ANSWER
if (initialItem.requestId == item.requestId) {
this.mainLoopS2CRequest.remove((initialItem.requestId));
assert (this.mainLoopS2CRequest.size() == item.requestId);
return item;
}
else {
item = null;
continue;
}
}
}
}
finally {
this.mainExchangeLock.unlock();
}
// initial != null && initial.waitForClient()
// && this.mainLoopC2SCommandFirst != null
// && this.mainLoopC2SCommandFirst.getCmdType() < MainCmdItem.T_S2C_C2S
// System.out.println("fromR evalDATA");
if (item == null) {
rProcessJEvents(this.rEngine);
continue;
}
switch (item.getCmdType()) {
case MainCmdItem.T_MAIN_CTRL_ITEM:
item = internalExecCtrl((MainCtrlCmdItem) item);
continue;
case MainCmdItem.T_DATA_ITEM:
item = internalEvalData((DataCmdItem) item);
continue;
case MainCmdItem.T_GRAPHICS_OP_ITEM:
item = internalExecGraOp((GraOpCmdItem) item);
continue;
case MainCmdItem.T_DBG_ITEM:
item = internalEvalDbg((DbgCmdItem) item);
continue;
case MainCmdItem.T_SRV_ITEM:
item = internalExecSrv(item);
continue;
default:
continue;
}
}
}
void domexAppend2C(final MainCmdItem item) {
if (this.mainLoopS2CNextCommandsFirst[0] == null) {
this.mainLoopS2CNextCommandsFirst[0]= this.mainLoopS2CNextCommandsLast[0]= item;
}
else {
this.mainLoopS2CNextCommandsLast[0]= this.mainLoopS2CNextCommandsLast[0].next= item;
}
}
void domexAppend2S(final MainCmdItem first) {
this.rni.rniInterrupted= false; // TODO remove, call always checkInterrupted before operations
if (this.mainLoopC2SCommandFirst == null) {
this.mainLoopC2SCommandFirst= first;
}
else {
MainCmdItem cmd= this.mainLoopC2SCommandFirst;
while (cmd.next != null) {
cmd= cmd.next;
}
cmd.next= first;
}
}
void domexInsert2S(final MainCmdItem first) {
if (this.mainLoopC2SCommandFirst != null) {
MainCmdItem cmd= first;
while (cmd.next != null) {
cmd= cmd.next;
}
cmd.next = this.mainLoopC2SCommandFirst;
}
this.mainLoopC2SCommandFirst= first;
}
void dorAppend2SConsoleAnswer(final String input) {
if (this.mainLoopPrompt == null) {
return;
}
this.mainLoopPrompt.setAnswer(input + '\n');
this.mainExchangeLock.lock();
try {
domexInsert2S(this.mainLoopPrompt);
}
finally {
this.mainExchangeLock.unlock();
}
}
public boolean inSafeMode() {
return this.safeMode;
}
public int beginSafeMode() {
if (this.safeMode) {
return 0;
}
try {
this.safeMode = true;
this.dbg.beginSafeMode();
return 1;
}
catch (final Exception e) {
LOGGER.log(Level.SEVERE, "An error occurred when running 'beginSafeMode' command.", e);
return -1;
}
}
public void endSafeMode(final int mode) {
if (mode > 0) {
try {
this.safeMode = false;
this.dbg.endSafeMode();
}
catch (final Exception e) {
LOGGER.log(Level.SEVERE, "An error occurred when running 'endSafeMode' command.", e);
}
}
}
/**
* Executes an {@link MainCtrlCmdItem R ctrl command}.
* Returns the result in the cmd object passed in, which is passed back out.
*
* @param cmd the command item
* @return the command item with setted answer
*/
private MainCtrlCmdItem internalExecCtrl(final MainCtrlCmdItem cmd) {
final byte savedSlot = this.currentSlot;
this.currentSlot = cmd.slot;
final boolean ownLock = this.rEngine.getRsync().safeLock();
try {
switch (cmd.getOp()) {
case MainCtrlCmdItem.OP_FINISH_TASK:
this.rEngine.jriFlushConsole();
break;
default:
break;
}
cmd.setAnswer(RjsStatus.OK_STATUS);
}
catch (final Throwable e) {
final String message = "Exec ctrl failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.SEVERE, message, e);
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_DATA_COMMON | 0x1),
"Internal server error (see server log)." ));
}
finally {
this.currentSlot = savedSlot;
if (ownLock) {
this.rEngine.getRsync().unlock();
}
}
return cmd.waitForClient() ? cmd : null;
}
/**
* Executes an {@link DataCmdItem R data command} (assignment, evaluation, ...).
* Returns the result in the cmd object passed in, which is passed back out.
*
* @param cmd the command item
* @return the command item with setted answer
*/
private DataCmdItem internalEvalData(final DataCmdItem cmd) {
final byte savedSlot = this.currentSlot;
this.currentSlot = cmd.slot;
final boolean ownLock = this.rEngine.getRsync().safeLock();
{ final byte depth = cmd.getDepth();
this.rni.newDataLevel((depth >= 0) ? depth : 128,
this.rniEnvsMaxLength, this.rniListsMaxLength );
}
final int savedProtected = this.rni.saveProtected();
final int savedSafeMode = beginSafeMode();
try {
if (this.rni.rniInterrupted) {
throw new CancellationException();
}
final Operation operation= cmd.getOperation();
final long envirP= this.rni.resolveEnvironment(cmd.getRho());
switch (operation.op) {
case DataCmdItem.FIND_DATA_OP:
final long[] foundP= rniFind(cmd.getDataText(), envirP, (cmd.getCmdOption() & 0x1000) != 0);
if (foundP != null) {
cmd.setAnswer((foundP[1] != 0) ?
this.rni.createDataObject(foundP[1], cmd.getCmdOption() & 0xfff) :
RMissing.INSTANCE,
this.rni.createEnvObject(foundP[0],
null, null, this.rEngine.rniGetLength(foundP[0]),
true ));
}
else {
cmd.setAnswer(null, null);
}
break;
case DataCmdItem.EVAL_NAMESPACE_DATA_OP:
cmd.setAnswer(this.rni.getNamespaceEnv(cmd.getDataText(), cmd.getCmdOption()), null);
break;
case DataCmdItem.EVAL_NAMESPACE_EXPORTS_DATA_OP:
cmd.setAnswer(this.rni.getNamespaceExportsEnv(cmd.getDataText(), cmd.getCmdOption()), null);
break;
default:
final long objP;
switch (operation.source) {
case Operation.NONE:
objP= 0;
break;
case Operation.EXPR:
objP= rniEval(cmd.getDataText(), envirP);
break;
case Operation.POINTER:
objP= Long.parseLong(cmd.getDataText());
break;
case Operation.FCALL:
objP= rniEval(cmd.getDataText(), (RList) cmd.getData(), envirP);
break;
case Operation.RDATA:
objP= this.rni.assignDataObject(cmd.getData());
break;
default:
throw new UnsupportedOperationException("source: " + operation.source); //$NON-NLS-1$
}
if (this.rni.rniInterrupted) {
throw new CancellationException();
}
switch (operation.target) {
case Operation.NONE:
break;
case Operation.EXPR:
rniAssign(cmd.getTargetExpr(), objP, envirP);
break;
}
if (operation.returnData) {
cmd.setAnswer(this.rni.createDataObject(objP, cmd.getCmdOption()), null);
}
else {
cmd.setAnswer(RjsStatus.OK_STATUS);
}
}
if (this.rni.rniInterrupted) {
throw new CancellationException();
}
}
catch (final RjsException e) {
cmd.setAnswer(e.getStatus());
}
catch (final CancellationException e) {
cmd.setAnswer(RjsStatus.CANCEL_STATUS);
}
catch (final UnsupportedOperationException e) {
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_DATA_COMMON | 0x2),
e.getMessage() ));
}
catch (final Throwable e) {
final String message = "Eval data failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.SEVERE, message, e);
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_DATA_COMMON | 0x1),
"Internal server error (see server log)." ));
}
finally {
this.currentSlot = savedSlot;
endSafeMode(savedSafeMode);
this.rni.looseProtected(savedProtected);
this.rni.exitDataLevel();
if (this.rni.rniInterrupted) {
doCheckInterrupted();
}
if (ownLock) {
this.rEngine.getRsync().unlock();
}
}
return cmd.waitForClient() ? cmd : null;
}
private long rniEval(final String expression, final long envirP) throws RjsException {
final long exprP= this.rni.resolveExpression(expression);
return this.rni.evalExpr(exprP, envirP, (CODE_DATA_EVAL_DATA | 0x3));
}
private long rniEval(final String name, final RList args, final long envirP) throws RjsException {
final long exprP= this.rni.createFCall(name, args);
return this.rni.evalExpr(exprP, envirP, (CODE_DATA_EVAL_DATA | 0x4));
}
private long[] rniFind(final String name, long envirP, final boolean inherits) throws RjsException {
final long symbolP= this.rEngine.rniInstallSymbol(name);
long p= this.rEngine.rniGetVarBySym(symbolP, envirP, Rengine.FLAG_UNBOUND_P);
if (inherits) {
while (p == this.rni.Unbound_P && (envirP= this.rEngine.rniParentEnv(envirP)) != 0
&& envirP != this.rni.Empty_EnvP ) {
p= this.rEngine.rniGetVarBySym(symbolP, envirP, Rengine.FLAG_UNBOUND_P);
}
}
if (p == this.rni.Unbound_P) {
return null;
}
if (p != 0) {
this.rni.protect(p);
}
return new long[] { envirP, p };
}
/**
* Creates and assigns an {@link RObject RJ R object} to an expression (e.g. symbol) in R.
*
* @param expression an expression the R object is assigned to
* @param obj an R object to assign
* @throws RjException
*/
private void rniAssign(final String expression, final long objP, final long envirP) throws RjsException {
if (objP == 0) {
throw new IllegalArgumentException("objP: 0x0"); //$NON-NLS-1$
}
long exprP= this.rni.protect(this.rni.resolveExpression(expression));
exprP= this.rEngine.rniCons(
this.rni.Assign_SymP, this.rEngine.rniCons(
exprP, this.rEngine.rniCons(
objP, this.rni.NULL_P,
0, false ),
0, false ),
0, true );
this.rni.evalExpr(exprP, 0, (CODE_DATA_ASSIGN_DATA | 0x3));
}
/**
* Performs a graphics operations
* Returns the result in the cmd object passed in, which is passed back out.
*
* @param cmd the command item
* @return the data command item with setted answer
*/
private GraOpCmdItem internalExecGraOp(final GraOpCmdItem cmd) {
final byte savedSlot = this.currentSlot;
this.currentSlot = cmd.slot;
final int savedProtected = this.rni.saveProtected();
final int savedSafeMode = beginSafeMode();
try {
CMD_OP: switch (cmd.getOp()) {
case GraOp.OP_CLOSE:
cmd.setAnswer(this.graphics.closeGraphic(cmd.getDevId()));
break CMD_OP;
case GraOp.OP_REQUEST_RESIZE:
cmd.setAnswer(this.graphics.resizeGraphic(cmd.getDevId()));
break CMD_OP;
case GraOp.OP_CONVERT_DEV2USER: {
final Coord coord = (Coord) cmd.getData();
final RjsStatus status = this.graphics.convertDev2User(cmd.getDevId(), coord);
if (status.getSeverity() == RjsStatus.OK) {
cmd.setAnswer(coord);
}
else {
cmd.setAnswer(status);
}
break CMD_OP; }
case GraOp.OP_CONVERT_USER2DEV: {
final Coord coord = (Coord) cmd.getData();
final RjsStatus status = this.graphics.convertUser2Dev(cmd.getDevId(), coord);
if (status.getSeverity() == RjsStatus.OK) {
cmd.setAnswer(coord);
}
else {
cmd.setAnswer(status);
}
break CMD_OP; }
default:
throw new IllegalStateException("Unsupported graphics operation " + cmd.toString());
}
return cmd;
}
// catch (final RjsException e) {
// cmd.setAnswer(e.getStatus());
// return cmd;
// }
catch (final CancellationException e) {
cmd.setAnswer(RjsStatus.CANCEL_STATUS);
return cmd;
}
catch (final Throwable e) {
final String message = "Eval data failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.SEVERE, message, e);
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_DATA_COMMON | 0x1),
"Internal server error (see server log)." ));
return cmd;
}
finally {
this.currentSlot = savedSlot;
endSafeMode(savedSafeMode);
this.rni.looseProtected(savedProtected);
}
}
private DbgCmdItem internalEvalDbg(final DbgCmdItem cmd) {
final byte savedSlot = this.currentSlot;
this.currentSlot = cmd.slot;
final boolean ownLock = this.rEngine.getRsync().safeLock();
this.rni.newDataLevel(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
final int savedProtected = this.rni.saveProtected();
final int savedSafeMode = beginSafeMode();
final RMainLoopCallbacks savedCallback = this.rEngine.getMainLoopCallbacks();
this.rEngine.addMainLoopCallbacks(this.hotModeCallbacks);
try {
if (this.rni.rniInterrupted) {
doCheckInterrupted();
}
CMD_OP: switch (cmd.getOp()) {
case DbgCmdItem.OP_LOAD_FRAME_LIST:
cmd.setAnswer(this.dbg.getCallStack());
break CMD_OP;
case DbgCmdItem.OP_LOAD_FRAME_CONTEXT:
cmd.setAnswer(this.dbg.loadFrameDetail(
(FrameContextDetailRequest) cmd.getData() ));
break CMD_OP;
case DbgCmdItem.OP_SET_DEBUG:
if (savedSafeMode != 0) { // was already inSafeMode
cmd.setAnswer(this.dbg.setDebug(
(SetDebugRequest) cmd.getData() ));
}
else {
cmd.setAnswer(new RjsStatus(RjsStatus.WARNING, 0, "Debug must not be set in data mode."));
}
break CMD_OP;
case DbgCmdItem.OP_CTRL_RESUME:
case DbgCmdItem.OP_CTRL_STEP_INTO:
case DbgCmdItem.OP_CTRL_STEP_OVER:
case DbgCmdItem.OP_CTRL_STEP_RETURN:
cmd.setAnswer(this.dbg.exec((DbgRequest) cmd.getData()));
break CMD_OP;
case DbgCmdItem.OP_REQUEST_SUSPEND:
this.dbg.requestSuspend();
cmd.setAnswer(RjsStatus.OK_STATUS);
break CMD_OP;
case DbgCmdItem.OP_INSTALL_TP_FLAGS:
cmd.setAnswer(this.dbg.getTracepointManager().installTracepoints(
(FlagTracepointInstallationRequest) cmd.getData() ));
break CMD_OP;
case DbgCmdItem.OP_INSTALL_TP_POSITIONS:
cmd.setAnswer(this.dbg.getTracepointManager().installTracepoints(
(ElementTracepointInstallationRequest) cmd.getData() ));
break CMD_OP;
case DbgCmdItem.OP_RESET_FILTER_STATE:
cmd.setAnswer(this.dbg.setFilterState(
(DbgFilterState) cmd.getData() ));
break CMD_OP;
case DbgCmdItem.OP_UPDATE_TP_STATES:
cmd.setAnswer(this.dbg.getTracepointManager().updateTracepointStates(
(TracepointStatesUpdate) cmd.getData() ));
break CMD_OP;
case DbgCmdItem.OP_SET_ENABLEMENT:
this.dbg.setEnablement((DbgEnablement) cmd.getData());
cmd.setAnswer(RjsStatus.OK_STATUS);
break CMD_OP;
default:
throw new IllegalStateException("Unsupported command.");
}
if (this.rni.rniInterrupted) {
throw new CancellationException();
}
}
catch (final RjsException e) {
final String message = "Eval dbg failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.WARNING, message, e);
cmd.setAnswer(e.getStatus());
}
catch (final CancellationException e) {
cmd.setAnswer(RjsStatus.CANCEL_STATUS);
}
catch (final Throwable e) {
final String message = "Eval dbg failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.SEVERE, message, e);
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_DBG_COMMON | 0x1),
"Internal server error (see server log)." ));
}
finally {
this.rEngine.addMainLoopCallbacks(savedCallback);
endSafeMode(savedSafeMode);
this.rni.looseProtected(savedProtected);
this.rni.exitDataLevel();
this.currentSlot = savedSlot;
if (this.rni.rniInterrupted) {
doCheckInterrupted();
}
if (ownLock) {
this.rEngine.getRsync().unlock();
}
}
return cmd.waitForClient() ? cmd : null;
}
private void doCheckInterrupted() {
this.mainInterruptLock.lock();
try {
if (this.rni.rniInterrupted) {
try {
Thread.sleep(10);
}
catch (final InterruptedException e) {}
this.rEngine.rniEval(this.rni.evalDummy_ExprP,
this.rni.rniSafeBaseExecEnvP );
this.rni.rniInterrupted = false;
}
}
catch (final Throwable e) {
LOGGER.log(Level.SEVERE, "An error occurred when resetting interrupted state.", e);
}
finally {
this.mainInterruptLock.unlock();
}
}
private RjsStatus internalAsyncDbg(final byte slot, final DbgCmdItem cmd) {
switch (cmd.getOp()) {
case DbgCmdItem.OP_RESET_FILTER_STATE:
return this.dbg.setFilterState(
(DbgFilterState) cmd.getData() );
case DbgCmdItem.OP_SET_ENABLEMENT:
this.dbg.setEnablement((DbgEnablement) cmd.getData());
return RjsStatus.OK_STATUS;
case DbgCmdItem.OP_UPDATE_TP_STATES:
return this.dbg.getTracepointManager().updateTracepointStates(
(TracepointStatesUpdate) cmd.getData() );
}
return new RjsStatus(RjsStatus.ERROR, CODE_DBG_COMMON | 0x2);
}
@Override
public void handle(final TracepointEvent event) {
internalMainFromR(new DbgCmdItem(DbgCmdItem.OP_NOTIFY_TP_EVENT, 0, event));
}
private MainCmdItem internalExecSrv(final MainCmdItem cmd) {
final byte savedSlot = this.currentSlot;
this.currentSlot = cmd.slot;
final boolean ownLock = this.rEngine.getRsync().safeLock();
this.rni.newDataLevel(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
final int savedProtected = this.rni.saveProtected();
final int savedSafeMode = beginSafeMode();
final RMainLoopCallbacks savedCallback = this.rEngine.getMainLoopCallbacks();
this.rEngine.addMainLoopCallbacks(this.hotModeCallbacks);
this.mainInterruptLock.lock();
try {
if (this.rni.rniInterrupted) {
doCheckInterrupted();
}
CMD_OP: switch (cmd.getOp()) {
case SrvCmdItem.OP_CLEAR_SESSION:
this.rni.evalExpr(this.rni.resolveExpression("rj:::tmp.clear()"),
this.rni.rniSafeGlobalExecEnvP, CODE_SRV_EVAL_DATA );
break CMD_OP;
}
}
catch (final RjsException e) {
final String message = "Exec srv failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.WARNING, message, e);
cmd.setAnswer(e.getStatus());
}
catch (final CancellationException e) {
cmd.setAnswer(RjsStatus.CANCEL_STATUS);
}
catch (final Throwable e) {
final String message = "Exec srv failed. Cmd:\n" + cmd.toString() + ".";
LOGGER.log(Level.SEVERE, message, e);
cmd.setAnswer(new RjsStatus(RjsStatus.ERROR, (CODE_SRV_COMMON | 0x1),
"Internal server error (see server log)." ));
}
finally {
this.mainInterruptLock.unlock();
this.rEngine.addMainLoopCallbacks(savedCallback);
endSafeMode(savedSafeMode);
this.rni.looseProtected(savedProtected);
this.rni.exitDataLevel();
this.currentSlot = savedSlot;
if (ownLock) {
this.rEngine.getRsync().unlock();
}
}
return null;
}
@Override
public String rReadConsole(final Rengine re, final String prompt, final int addToHistory) {
if (prompt.startsWith("Browse")) { //$NON-NLS-1$
final String input= this.dbg.handleBrowserPrompt(prompt);
if (input != null) {
return input;
}
}
else if (inSafeMode()) {
return "\n"; //$NON-NLS-1$
}
else {
this.dbg.clearContext();
}
this.mainLoopPrompt= new ConsoleReadCmdItem((addToHistory == 1) ? V_TRUE : V_FALSE, prompt);
final MainCmdItem cmd= internalMainFromR(this.mainLoopPrompt);
this.mainLoopPrompt= null;
if (cmd.isOK()) {
return cmd.getDataText();
}
return "\n"; //$NON-NLS-1$
}
@Override
public void rWriteConsole(final Rengine re, final String text, final int type) {
final byte streamId = (type == 0) ? ConsoleWriteCmdItem.R_OUTPUT : ConsoleWriteCmdItem.R_ERROR;
this.mainExchangeLock.lock();
try {
this.ioStreams.domexAppendOut(streamId, text);
if (this.mainLoopClientListen > 0) {
this.mainExchangeClient.signalAll();
}
return;
}
finally {
this.mainExchangeLock.unlock();
}
}
@Override
public void rFlushConsole(final Rengine re) {
this.mainExchangeLock.lock();
try {
this.ioStreams.domexFlushOut();
if (this.mainLoopClientListen > 0) {
this.mainExchangeClient.signalAll();
}
return;
}
finally {
this.mainExchangeLock.unlock();
}
}
@Override
public void rBusy(final Rengine re, final int which) {
this.mainLoopBusyAtServer = (which == 1);
internalMainFromR(null);
}
@Override
public void rShowMessage(final Rengine re, final String message) {
internalMainFromR(new ConsoleMessageCmdItem(message));
}
@Override
public String rChooseFile(final Rengine re, final int newFile) {
final RList args = this.rObjectFactory.createList(new RObject[] {
this.rObjectFactory.createVector(this.rObjectFactory.createLogiData(new boolean[] {
(newFile == 1),
} )),
}, new String[] {
"newResource",
} );
final RList answer = execUICommand("common/chooseFile", args, true);
if (answer != null) {
final RObject filenameObject = answer.get("filename");
if (RDataUtil.isSingleString(filenameObject)) {
return filenameObject.getData().getChar(0);
}
}
return null;
}
@Override
public void rLoadHistory(final Rengine re, final String filename) {
final RList args = this.rObjectFactory.createList(new RObject[] {
this.rObjectFactory.createVector(this.rObjectFactory.createCharData(new String[] {
filename,
} )),
}, new String[] {
"filename",
} );
execUICommand("common/loadHistory", args, true);
}
@Override
public void rSaveHistory(final Rengine re, final String filename) {
final RList args = this.rObjectFactory.createList(new RObject[] {
this.rObjectFactory.createVector(this.rObjectFactory.createCharData(new String[] {
filename,
} )),
}, new String[] {
"filename",
} );
execUICommand("common/saveHistory", args, true);
}
@Override
public long rExecJCommand(final Rengine re, String commandId, final long argsP, final int options) {
try {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Executing java command ''{0}''.", commandId);
}
final int idx = commandId.indexOf(':');
if (idx <= 0) {
return 0;
}
final String commandGroup = commandId.substring(0, idx);
commandId = commandId.substring(idx+1);
if (commandGroup.equals("ui")) {
final boolean wait = ((options | 1) != 0);
final RList answer = execUICommand(commandId, createJCommandArgs(argsP), wait);
return createJCommandAnswer(answer);
}
if (commandGroup.equals("dbg") && this.dbg != null) {
return this.dbg.execRJCommand(commandId, argsP);
}
}
catch (final Exception e) {
LOGGER.log(Level.WARNING, "An error occurred when executing java command '" + commandId + "'.", e);
}
return 0;
}
private RList createJCommandArgs(final long p) {
if (p != 0) {
this.rni.newDataLevel(255, this.rniEnvsMaxLength, this.rniListsMaxLength);
final int savedProtected = this.rni.saveProtected();
try {
this.rni.protect(p);
final RObject rObject = this.rni.createDataObject(p, 0, EVAL_MODE_DEFAULT);
if (rObject.getRObjectType() == RObject.TYPE_LIST) {
return (RList) rObject;
}
}
finally {
this.rni.looseProtected(savedProtected);
this.rni.exitDataLevel();
}
}
return null;
}
private long createJCommandAnswer(final RList answer) throws RjsException {
if (answer != null) {
this.rni.newDataLevel(255, this.rniEnvsMaxLength, this.rniListsMaxLength);
final int savedProtected = this.rni.saveProtected();
try {
return this.rni.assignDataObject(answer);
}
finally {
this.rni.looseProtected(savedProtected);
this.rni.exitDataLevel();
}
}
return 0;
}
public RList execUICommand(final String command, final RList args, final boolean wait) {
if (command == null) {
throw new NullPointerException("command");
}
final MainCmdItem answer = internalMainFromR(new ExtUICmdItem(command, 0, args, wait));
if (wait && answer instanceof ExtUICmdItem && answer.isOK()) {
return ((ExtUICmdItem) answer).getDataArgs();
}
return null;
}
@Override
public void rProcessJEvents(final Rengine re) {
while (true) {
this.mainExchangeLock.lock();
try {
if (!this.hotModeRequested) {
return;
}
if (this.hotMode || this.mainLoopState == ENGINE_WAIT_FOR_CLIENT
|| this.rEngine.getMainLoopCallbacks() != JRIServer.this) {
this.hotModeRequested = false;
this.hotModeDelayed = true;
return;
}
this.hotModeRequested = false;
this.hotMode = true;
}
finally {
this.mainExchangeLock.unlock();
}
final int savedSafeMode = beginSafeMode();
try {
this.rEngine.jriFlushConsole();
this.rEngine.addMainLoopCallbacks(this.hotModeCallbacks);
internalMainFromR(new ConsoleReadCmdItem(2, ""));
}
catch (final Throwable e) {
LOGGER.log(Level.SEVERE, "An error occured when running hot mode.", e);
}
finally {
endSafeMode(savedSafeMode);
this.mainExchangeLock.lock();
try {
this.rEngine.addMainLoopCallbacks(JRIServer.this);
this.hotMode = false;
if (this.hotModeDelayed) {
this.hotModeDelayed = false;
this.hotModeRequested = true;
}
if (!this.hotModeRequested) {
return;
}
}
finally {
this.mainExchangeLock.unlock();
}
}
}
}
private void internalClearClient(final Client client) {
final MainCmdC2SList list = new MainCmdC2SList();
final int savedClientState = this.mainLoopClient0State;
this.mainLoopClient0State = CLIENT_OK;
final byte slot = client.slot;
try {
if (slot == 0) {
try {
while (this.hotMode) {
final MainCmdItem cmdItem = this.mainLoopS2CRequest.get(this.mainLoopS2CRequest.size()-1);
cmdItem.setAnswer(RjsStatus.CANCEL_STATUS);
list.setObjects(cmdItem);
if (this.mainLoopS2CNextCommandsFirst[slot] == cmdItem) {
this.mainLoopS2CNextCommandsFirst[slot] = null;
}
else {
MainCmdItem item = this.mainLoopS2CNextCommandsFirst[slot];
while (item != null) {
if (item.next == cmdItem) {
item.next = null;
break;
}
item = item.next;
}
}
internalMainCallbackFromClient((byte) 0, list);
}
}
catch (final Exception e) {
LOGGER.log(Level.SEVERE, "An error occurrend when trying to cancel hot loop.", e);
}
domexAppend2S(new SrvCmdItem(SrvCmdItem.OP_CLEAR_SESSION));
this.mainExchangeR.signalAll();
}
}
finally {
this.mainLoopClient0State = savedClientState;
}
}
private void internalRStopped() {
this.mainExchangeLock.lock();
try {
if (this.mainLoopState == ENGINE_STOPPED) {
return;
}
this.hotMode = false;
this.mainLoopState = ENGINE_STOPPED;
this.mainExchangeClient.signalAll();
while (this.mainLoopS2CNextCommandsFirst != null || this.ioStreams.domexHasOut()) {
try {
this.mainExchangeR.awaitNanos(100000000L);
}
catch (final InterruptedException e) {}
this.mainExchangeClient.signalAll();
}
}
finally {
this.mainExchangeLock.unlock();
}
final ServerRuntimePlugin[] plugins;
synchronized (this.pluginsLock) {
plugins = this.pluginsList;
this.pluginsList = new ServerRuntimePlugin[0];
}
for (int i = 0; i < plugins.length; i++) {
try {
plugins[i].rjStop(V_OK);
}
catch (final Throwable e) {
handlePluginError(plugins[i], e);
}
}
synchronized (this) {
this.serverState = S_STOPPED;
this.rEngine = null;
}
}
@Override
public void onRExit() {
internalRStopped();
super.onRExit();
}
@Override
public MainCmdItem sendMainCmd(final MainCmdItem cmd) {
return internalMainFromR(cmd);
}
}