| /*=============================================================================# |
| # Copyright (c) 2008, 2019 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.internal.r.console.ui.launching; |
| |
| import java.net.InetAddress; |
| import java.net.MalformedURLException; |
| import java.rmi.RemoteException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.ILaunchManager; |
| import org.eclipse.debug.core.model.IProcess; |
| import org.eclipse.debug.core.model.LaunchConfigurationDelegate; |
| import org.eclipse.debug.ui.IDebugUIConstants; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.jcommons.rmi.RMIAddress; |
| import org.eclipse.statet.jcommons.rmi.RMIRegistry; |
| import org.eclipse.statet.jcommons.rmi.RMIRegistryManager; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.status.WarningStatus; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolService; |
| |
| import org.eclipse.statet.ecommons.debug.core.util.LaunchUtils; |
| import org.eclipse.statet.ecommons.debug.ui.util.UnterminatedLaunchAlerter; |
| import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| |
| import org.eclipse.statet.internal.r.console.ui.Messages; |
| import org.eclipse.statet.internal.r.console.ui.RConsoleUIPlugin; |
| import org.eclipse.statet.nico.core.runtime.ILogOutput; |
| import org.eclipse.statet.nico.core.runtime.ToolRunner; |
| import org.eclipse.statet.nico.core.util.HistoryTrackingConfiguration; |
| import org.eclipse.statet.nico.core.util.TrackingConfiguration; |
| import org.eclipse.statet.nico.ui.NicoUITools; |
| import org.eclipse.statet.nico.ui.console.NIConsoleColorAdapter; |
| import org.eclipse.statet.nico.ui.util.WorkbenchStatusHandler; |
| import org.eclipse.statet.r.console.core.IRDataAdapter; |
| import org.eclipse.statet.r.console.core.RProcess; |
| import org.eclipse.statet.r.console.core.RWorkspace; |
| import org.eclipse.statet.r.console.ui.RConsole; |
| import org.eclipse.statet.r.console.ui.launching.RConsoleLaunching; |
| import org.eclipse.statet.r.console.ui.tools.REnvAutoUpdater; |
| import org.eclipse.statet.r.console.ui.tools.REnvIndexAutoUpdater; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.pkgmanager.IRPkgManager; |
| import org.eclipse.statet.r.core.renv.IREnvConfiguration; |
| import org.eclipse.statet.r.launching.core.ILaunchDelegateAddon; |
| import org.eclipse.statet.r.launching.core.RLaunching; |
| import org.eclipse.statet.r.nico.RWorkspaceConfig; |
| import org.eclipse.statet.r.nico.impl.RjsController; |
| import org.eclipse.statet.r.nico.impl.RjsController.RjsConnection; |
| import org.eclipse.statet.rj.data.RDataUtils; |
| import org.eclipse.statet.rj.data.UnexpectedRDataException; |
| import org.eclipse.statet.rj.renv.core.REnvConfiguration; |
| import org.eclipse.statet.rj.server.RjsComConfig; |
| import org.eclipse.statet.rj.services.FunctionCall; |
| import org.eclipse.statet.rj.services.RVersion; |
| |
| |
| /** |
| * Launch delegate for RJ based R console using local RJ server |
| */ |
| public class RConsoleRJLaunchDelegate extends LaunchConfigurationDelegate { |
| |
| |
| static final long TIMEOUT= 60L * 1000000000L; |
| |
| static final RVersion VERSION_2_12_0= new RVersion(2, 12, 0); |
| |
| static class ConfigRunnable implements SystemRunnable { |
| |
| |
| private final Tool tool; |
| private final boolean enableRHelp; |
| private final boolean enableRGraphics; |
| private final boolean enableRDbgExt; |
| private final boolean enableRDbg; |
| |
| public ConfigRunnable(final Tool tool, final boolean enableRHelp, |
| final boolean enableRGraphics, |
| final boolean enableRDbgExt, final boolean enableRDbg) { |
| this.tool= tool; |
| this.enableRHelp= enableRHelp; |
| this.enableRGraphics= enableRGraphics; |
| this.enableRDbgExt= enableRDbgExt; |
| this.enableRDbg= enableRDbg; |
| } |
| |
| |
| @Override |
| public String getTypeId() { |
| return "r/integration"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public String getLabel() { |
| return "Initialize R-StatET Tools"; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return (tool == this.tool); |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| if ((event & MASK_EVENT_GROUP) == REMOVING_EVENT_GROUP) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, final ProgressMonitor m) throws StatusException { |
| final IRDataAdapter r= (IRDataAdapter) service; |
| r.briefAboutToChange(); |
| try { |
| final RVersion rVersion= r.getPlatform().getRVersion(); |
| if (rVersion.compareTo(VERSION_2_12_0) < 0) { |
| r.evalVoid("library('rj')", m); //$NON-NLS-1$ |
| } |
| else { |
| r.evalVoid("library('rj', quietly= TRUE)", m); //$NON-NLS-1$ |
| } |
| if (this.enableRHelp) { |
| final FunctionCall fcall= r.createFunctionCall(".statet.initHelp"); //$NON-NLS-1$ |
| fcall.evalVoid(m); |
| } |
| if (this.enableRGraphics) { |
| try { |
| final FunctionCall fcall= r.createFunctionCall(".rj.initGD"); //$NON-NLS-1$ |
| fcall.addLogi("default", true); //$NON-NLS-1$ |
| RDataUtils.checkSingleLogiValue(fcall.evalData(m)); |
| } |
| catch (final StatusException | UnexpectedRDataException e) { |
| r.handleStatus(new WarningStatus(RConsoleUIPlugin.BUNDLE_ID, |
| "The graphic device for the R Graphic view cannot be initialized.", e), |
| m ); |
| } |
| } |
| { final FunctionCall fcall= r.createFunctionCall(".statet.initDebug"); //$NON-NLS-1$ |
| fcall.addLogi("mode", this.enableRDbg); //$NON-NLS-1$ |
| fcall.addLogi("ext", this.enableRDbgExt); //$NON-NLS-1$ |
| fcall.evalVoid(m); |
| } |
| } |
| finally { |
| r.briefChanged(RWorkspace.REFRESH_COMPLETE); |
| } |
| } |
| |
| } |
| |
| static RWorkspaceConfig createWorkspaceConfig(final ILaunchConfiguration configuration) throws CoreException { |
| final RWorkspaceConfig config= new RWorkspaceConfig(); |
| config.setEnableObjectDB(configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_ENABLED, true)); |
| config.setEnableAutoRefresh(configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_AUTOREFRESH_ENABLED, true)); |
| return config; |
| } |
| |
| static void initConsoleOptions(final RjsController controller, |
| final REnvConfiguration rEnvConfig, |
| final ILaunchConfiguration configuration, final String mode, |
| final boolean isStartup) throws CoreException { |
| if (rEnvConfig != null) { |
| final IRPkgManager manager= RCore.getRPkgManager(rEnvConfig.getREnv()); |
| REnvAutoUpdater.connect(controller, manager); |
| REnvIndexAutoUpdater.connect(controller.getTool(), manager); |
| } |
| controller.addStartupRunnable(new ConfigRunnable( |
| controller.getTool(), |
| (rEnvConfig != null && configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RHELP_ENABLED, true)), |
| configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RGRAPHICS_ASDEFAULT, true), |
| configuration.getAttribute(RConsoleOptionsTab.ATTR_INTEGRATION_RDBGEXT_ENABLED, true), |
| mode.equals(ILaunchManager.DEBUG_MODE) )); |
| if (isStartup) { |
| RConsoleLaunching.scheduleStartupSnippet(controller, configuration); |
| } |
| } |
| |
| |
| private ILaunchDelegateAddon addon; |
| |
| |
| public RConsoleRJLaunchDelegate() { |
| } |
| |
| public RConsoleRJLaunchDelegate(final ILaunchDelegateAddon addon) { |
| this.addon= addon; |
| } |
| |
| |
| @Override |
| public void launch(final ILaunchConfiguration configuration, final String mode, |
| final ILaunch launch, final IProgressMonitor monitor) throws CoreException { |
| final SubMonitor m= LaunchUtils.initProgressMonitor(configuration, monitor, 25); |
| final long timestamp= System.currentTimeMillis(); |
| |
| final IWorkbenchPage page= UIAccess.getActiveWorkbenchPage(false); |
| |
| m.worked(1); |
| if (m.isCanceled()) { |
| return; |
| } |
| |
| // load tracking configurations |
| final List<TrackingConfiguration> trackingConfigs; |
| { final List<String> trackingIds= configuration.getAttribute(RConsoleOptionsTab.TRACKING_ENABLED_IDS, Collections.EMPTY_LIST); |
| trackingConfigs= new ArrayList<>(trackingIds.size()); |
| for (final String id : trackingIds) { |
| final TrackingConfiguration trackingConfig; |
| if (id.equals(HistoryTrackingConfiguration.HISTORY_TRACKING_ID)) { |
| trackingConfig= new HistoryTrackingConfiguration(id); |
| } |
| else { |
| trackingConfig= new TrackingConfiguration(id); |
| } |
| RConsoleOptionsTab.TRACKING_UTIL.load(trackingConfig, configuration); |
| trackingConfigs.add(trackingConfig); |
| } |
| } |
| |
| m.worked(1); |
| if (m.isCanceled()) { |
| return; |
| } |
| |
| // r env |
| final IREnvConfiguration rEnv= RLaunching.getREnvConfig(configuration, true); |
| |
| final RMIRegistry registry; |
| boolean requireCodebase; |
| { final String s= System.getProperty("org.eclipse.statet.r.console.rmiRegistryPort"); |
| int port= -1; |
| if (s != null && s.length() > 0) { |
| try { |
| m.subTask(Messages.LaunchDelegate_CheckingRegistry_subtask); |
| port= Integer.parseInt(s); |
| final RMIAddress registryAddress= new RMIAddress(InetAddress.getLoopbackAddress(), port, null); |
| registry= new RMIRegistry(registryAddress, true); |
| requireCodebase= true; |
| } |
| catch (final NumberFormatException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0, |
| "The registry port specified by 'org.eclipse.statet.r.console.rmiRegistryPort' is invalid.", e )); |
| } |
| catch (final MalformedURLException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0, |
| "The registry port specified by 'org.eclipse.statet.r.console.rmiRegistryPort' is invalid.", e )); |
| } |
| catch (final RemoteException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, 0, |
| "Connection setup to the registry specified by 'org.eclipse.statet.r.console.rmiRegistryPort' failed.", e )); |
| } |
| } |
| else { |
| try { |
| registry= RMIRegistryManager.INSTANCE.getEmbeddedPrivateRegistry( |
| StatusUtils.convertChild(m.newChild(1)) ); |
| } |
| catch (final StatusException e) { |
| throw StatusUtils.convert(e); |
| } |
| requireCodebase= false; |
| } |
| } |
| final RMIAddress rmiAddress= new RMIAddress(InetAddress.getLoopbackAddress(), registry.getAddress().getPort(), |
| "rjs-local-" + System.currentTimeMillis() ); //$NON-NLS-1$ |
| final RJEngineLaunchDelegate engineLaunchDelegate= new RJEngineLaunchDelegate( |
| rmiAddress.getAddress(), requireCodebase, rEnv); |
| |
| m.worked(1); |
| if (m.isCanceled()) { |
| return; |
| } |
| |
| // start server |
| m.subTask(Messages.LaunchDelegate_StartREngine_subtask); |
| try { |
| RjsComConfig.setRMIClientSocketFactory(null); |
| |
| engineLaunchDelegate.launch(configuration, mode, launch, m.newChild(10)); |
| final IProcess[] processes= launch.getProcesses(); |
| if (processes.length == 0) { |
| return; |
| } |
| |
| m.worked(1); |
| if (m.isCanceled()) { |
| return; |
| } |
| |
| // arguments |
| final String[] rArgs= LaunchUtils.getProcessArguments(configuration, RConsoleLaunching.ATTR_OPTIONS); |
| |
| m.worked(1); |
| if (m.isCanceled()) { |
| return; |
| } |
| |
| // create process |
| UnterminatedLaunchAlerter.registerLaunchType(RConsoleLaunching.R_CONSOLE_CONFIGURATION_TYPE_ID); |
| |
| final RProcess process= new RProcess(launch, rEnv, |
| LaunchUtils.createLaunchPrefix(configuration), |
| rEnv.getName() + " / RJ " + LaunchUtils.createProcessTimestamp(timestamp), //$NON-NLS-1$ |
| rmiAddress.toString(), |
| null, // wd is set at rjs startup |
| timestamp ); |
| process.setAttribute(IProcess.ATTR_CMDLINE, rmiAddress.toString() + '\n' + Arrays.toString(rArgs)); |
| |
| // Wait until the engine is started or died |
| m.subTask(Messages.LaunchDelegate_WaitForR_subtask); |
| final long t= System.nanoTime(); |
| WAIT: for (int i= 0; true; i++) { |
| if (processes[0].isTerminated()) { |
| final boolean silent= configuration.getAttribute(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, true); |
| final IStatus logStatus= ToolRunner.createOutputLogStatus( |
| processes[0].getAdapter(ILogOutput.class) ); |
| // move to R server? |
| final StringBuilder sb= new StringBuilder(); |
| sb.append("Launching the R Console was cancelled, because it seems starting the R engine failed."); |
| sb.append(engineLaunchDelegate.getDebugInfo()); |
| |
| StatusManager.getManager().handle(new Status(silent ? IStatus.INFO : IStatus.ERROR, |
| RConsoleUIPlugin.BUNDLE_ID, sb.toString(), |
| (logStatus != null) ? new CoreException(logStatus) : null ), |
| silent ? (StatusManager.LOG) : (StatusManager.LOG | StatusManager.SHOW) ); |
| return; |
| } |
| if (m.isCanceled()) { |
| processes[0].terminate(); |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| try { |
| final String[] list= registry.getRegistry().list(); |
| for (final String entry : list) { |
| if (entry.equals(rmiAddress.getName())) { |
| break WAIT; |
| } |
| } |
| if (i > 1 && System.nanoTime() - t > TIMEOUT) { |
| break WAIT; |
| } |
| } |
| catch (final RemoteException e) { |
| if (i > 0 && System.nanoTime() - t > TIMEOUT / 3) { |
| break WAIT; |
| } |
| } |
| try { |
| Thread.sleep(333); |
| } |
| catch (final InterruptedException e) { |
| // continue, monitor and process is checked |
| } |
| } |
| m.worked(5); |
| |
| final RjsConnection connection= RjsController.lookup(registry.getRegistry(), null, rmiAddress); |
| |
| final HashMap<String, Object> rjsProperties= new HashMap<>(); |
| rjsProperties.put(RjsComConfig.RJ_DATA_STRUCTS_LISTS_MAX_LENGTH_PROPERTY_ID, |
| configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_LISTS_MAX_LENGTH, 10000)); |
| rjsProperties.put(RjsComConfig.RJ_DATA_STRUCTS_ENVS_MAX_LENGTH_PROPERTY_ID, |
| configuration.getAttribute(RConsoleLaunching.ATTR_OBJECTDB_ENVS_MAX_LENGTH, 10000)); |
| rjsProperties.put("rj.session.startup.time", timestamp); //$NON-NLS-1$ |
| final RjsController controller= new RjsController(process, rmiAddress, connection, null, |
| (RjsController.RJS_LOCAL | RjsController.RJS_SETUP_CONSOLE), rArgs, rjsProperties, |
| engineLaunchDelegate.getWorkingDirectory(), |
| createWorkspaceConfig(configuration), trackingConfigs); |
| process.init(controller); |
| RConsoleLaunching.registerDefaultHandlerTo(controller); |
| |
| m.worked(5); |
| |
| initConsoleOptions(controller, rEnv, configuration, mode, true); |
| |
| if (this.addon != null) { |
| this.addon.init(configuration, mode, controller, m); |
| } |
| |
| final RConsole console= new RConsole(process, new NIConsoleColorAdapter()); |
| NicoUITools.startConsoleLazy(console, page, |
| configuration.getAttribute(RConsoleLaunching.ATTR_PIN_CONSOLE, false)); |
| |
| new ToolRunner().runInBackgroundThread(process, new WorkbenchStatusHandler()); |
| } |
| finally { |
| RjsComConfig.clearRMIClientSocketFactory(); |
| } |
| |
| m.done(); |
| } |
| |
| } |