| /*=============================================================================# |
| # Copyright (c) 2017, 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.apps.ui.launching; |
| |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.variables.IStringVariable; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.ui.IViewPart; |
| import org.eclipse.ui.IViewReference; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.browser.IWebBrowser; |
| import org.eclipse.ui.browser.IWorkbenchBrowserSupport; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import com.jcraft.jsch.JSchException; |
| import com.jcraft.jsch.Session; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.InfoStatus; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.ts.core.RunnableStatus; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| |
| import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils; |
| import org.eclipse.statet.ecommons.ui.mpbv.BrowserSession; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.variables.core.StaticVariable; |
| import org.eclipse.statet.ecommons.variables.core.VariableText2; |
| import org.eclipse.statet.ecommons.variables.core.VariableUtils; |
| |
| import org.eclipse.statet.internal.r.apps.ui.Messages; |
| import org.eclipse.statet.internal.r.apps.ui.RAppUIPlugin; |
| import org.eclipse.statet.internal.r.apps.ui.variables.AppVarView; |
| import org.eclipse.statet.internal.r.apps.ui.viewer.AppBrowserSession; |
| import org.eclipse.statet.internal.r.apps.ui.viewer.AppBrowserView; |
| import org.eclipse.statet.nico.core.runtime.Queue; |
| import org.eclipse.statet.nico.core.runtime.ToolController; |
| import org.eclipse.statet.nico.ui.NicoUI; |
| import org.eclipse.statet.nico.ui.NicoUITools; |
| import org.eclipse.statet.r.apps.ui.AppRegistry; |
| import org.eclipse.statet.r.apps.ui.RApp; |
| import org.eclipse.statet.r.apps.ui.VariablesData; |
| import org.eclipse.statet.r.console.core.RConsoleTool; |
| import org.eclipse.statet.r.console.core.RProcess; |
| import org.eclipse.statet.r.console.core.RWorkspace; |
| import org.eclipse.statet.r.console.core.util.RCodeVariableText; |
| import org.eclipse.statet.r.core.tool.AbstractStatetRRunnable; |
| import org.eclipse.statet.r.core.tool.IRConsoleService; |
| import org.eclipse.statet.r.nico.impl.RjsUtil; |
| import org.eclipse.statet.rj.ts.core.AbstractRToolRunnable; |
| import org.eclipse.statet.rj.ts.core.RToolService; |
| |
| |
| @NonNullByDefault |
| public class AppRunner extends AbstractStatetRRunnable implements RApp { |
| |
| |
| public static final String RUN_TASK_ID= "org.eclipse.statet.r.apps/RunApp"; //$NON-NLS-1$ |
| public static final String STOP_TASK_ID= "org.eclipse.statet.r.apps/StopApp"; //$NON-NLS-1$ |
| |
| private static final String LOCALHOST= "127.0.0.1"; //$NON-NLS-1$ |
| |
| private static final Status NOT_RUNNING_DATA_STATUS= new InfoStatus(RAppUIPlugin.BUNDLE_ID, |
| "The app is not running." ); |
| private static final Status NOT_LOADED_DATA_STATUS= new InfoStatus(RAppUIPlugin.BUNDLE_ID, |
| "Variabes are not yet available." ); |
| |
| |
| public static RProcess fetchRProcess(final IWorkbenchPage page) throws CoreException { |
| final Tool tool= NicoUI.getToolRegistry().getActiveToolSession(page).getTool(); |
| NicoUITools.accessTool(RConsoleTool.TYPE, tool); |
| return (RProcess)tool; |
| } |
| |
| |
| private class AppSession { |
| |
| private final RProcess rProcess; |
| |
| private Queue.Section queueSection; |
| |
| private String host; |
| private String remoteHost; |
| private int remotePort; |
| private @Nullable Session sshSession; |
| private int sshLocalPort= -1; |
| |
| private @Nullable URL localUrl; |
| private @Nullable URL idUrl; |
| |
| private boolean isRunning; |
| |
| private long startedTimestamp; |
| |
| |
| public AppSession(final RProcess tool) { |
| this.rProcess= tool; |
| } |
| |
| |
| public RProcess getTool() { |
| return this.rProcess; |
| } |
| |
| public boolean isRunning() { |
| return this.isRunning; |
| } |
| |
| public void init(final ProgressMonitor m) throws StatusException { |
| { this.queueSection= this.rProcess.getController().getCurrentQueueSection(); |
| } |
| { final String host= AppRunner.this.config.getAppHost(); |
| if (host.isEmpty()) { |
| final RWorkspace workspaceData= this.rProcess.getWorkspaceData(); |
| if (workspaceData.isRemote()) { |
| this.host= workspaceData.getRemoteAddress(); |
| final Map<String, Object> connectionInfo= this.rProcess.getConnectionInfo(); |
| if (connectionInfo != null && Objects.equals(connectionInfo.get("protocol"), "ssh")) { |
| this.remoteHost= LOCALHOST; |
| this.sshSession= RjsUtil.getSession(connectionInfo, m); |
| } |
| } |
| else { |
| this.host= LOCALHOST; |
| } |
| } |
| else { |
| this.host= host; |
| } |
| if (this.remoteHost == null) { |
| this.remoteHost= this.host; |
| } |
| this.remotePort= AppRunner.this.config.getAppPort(); |
| } |
| } |
| |
| public String getStartCode() throws StatusException { |
| { final String code= AppRunner.this.config.getStartCode(); |
| |
| final Map<String, IStringVariable> variables= AppRunner.this.config.getVariables(); |
| VariableUtils.add(variables, new StaticVariable( |
| AppControlConfigs.APP_HOST_VAR, |
| this.remoteHost )); |
| VariableUtils.add(variables, new StaticVariable( |
| AppControlConfigs.APP_PORT_VAR, |
| (this.remotePort > 0) ? Integer.toString(this.remotePort) : "NULL" )); //$NON-NLS-1$ |
| |
| final VariableText2 variableText= new RCodeVariableText( |
| this.rProcess.getWorkspaceData(), variables ); |
| try { |
| return variableText.performStringSubstitution(code, null); |
| } |
| catch (final CoreException e) { |
| throw new StatusException(new ErrorStatus(RAppUIPlugin.BUNDLE_ID, |
| NLS.bind(Messages.Operation_StartApp_RCode_error_SpecInvalid_message, |
| e.getMessage() ))); |
| } |
| } |
| } |
| |
| public boolean onStarted(final String url) { |
| try { |
| final URL rUrl; |
| try { |
| rUrl= new URL(url); |
| } |
| catch (final MalformedURLException e) { |
| throw new StatusException(new ErrorStatus(RAppUIPlugin.BUNDLE_ID, |
| NLS.bind("Invalid URL of the R app from R= ''{0}''.", url), |
| e )); |
| } |
| |
| if (this.sshSession != null) { |
| try { |
| this.sshLocalPort= this.sshSession.setPortForwardingL(0, LOCALHOST, |
| (rUrl.getPort() != -1) ? rUrl.getPort() : 80 ); |
| this.localUrl= new URL(rUrl.getProtocol(), LOCALHOST, this.sshLocalPort, |
| rUrl.getFile() ); |
| this.idUrl= new URL(rUrl.getProtocol(), this.host, rUrl.getPort(), |
| rUrl.getFile() ); |
| } |
| catch (final JSchException e) { |
| throw new StatusException(new ErrorStatus(RAppUIPlugin.BUNDLE_ID, |
| "Failed create SSH tunnel for http connection of the R app.", |
| e )); |
| } |
| } |
| else if (!this.host.equals(rUrl.getHost())) { |
| this.localUrl= new URL(rUrl.getProtocol(), this.host, rUrl.getPort(), |
| rUrl.getFile() ); |
| this.idUrl= this.localUrl; |
| } |
| else { |
| this.localUrl= rUrl; |
| this.idUrl= this.localUrl; |
| } |
| } |
| catch (final Exception e) { |
| onError(IStatus.ERROR, "An error occurred when preparing to show the R app.", e, |
| (AppRunner.this.config.getViewerId() != null) ? |
| StatusManager.LOG | StatusManager.SHOW : |
| StatusManager.LOG ); |
| return false; |
| } |
| |
| synchronized (AppRunner.this) { |
| this.isRunning= true; |
| this.startedTimestamp= System.nanoTime(); |
| return true; |
| } |
| } |
| |
| public URL getLocalUrl() { |
| return this.localUrl; |
| } |
| |
| public URL getIdUrl() { |
| return this.idUrl; |
| } |
| |
| public void onAppStop() { |
| synchronized (AppRunner.this) { |
| this.isRunning= false; |
| } |
| |
| this.queueSection= null; |
| |
| if (this.sshSession != null && this.sshLocalPort > 0) { |
| try { |
| this.sshSession.delPortForwardingL(this.sshLocalPort); |
| } |
| catch (final JSchException e) { |
| RAppUIPlugin.logError("Failed delete SSH tunnel for http connection of the R app.", |
| e ); |
| } |
| this.sshLocalPort= -1; |
| } |
| } |
| |
| } |
| |
| public class StopRunnable extends AbstractRToolRunnable { |
| |
| |
| public StopRunnable() { |
| super(STOP_TASK_ID, "Stop R App"); //$NON-NLS-1$ |
| } |
| |
| |
| @Override |
| public boolean changed(final int event, final Tool tool) { |
| switch (event) { |
| case MOVING_FROM: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| @Override |
| protected void run(final RToolService r, |
| final ProgressMonitor m) throws StatusException { |
| AppSession session; |
| synchronized (AppRunner.this) { |
| session= AppRunner.this.session; |
| if (session == null || !session.isRunning()) { |
| return; |
| } |
| } |
| |
| final String code= AppRunner.this.config.getStopCode(); |
| r.evalVoid(code, m); |
| } |
| |
| } |
| |
| |
| private final AppControlLaunchConfig config; |
| |
| private final CopyOnWriteIdentityListSet<Listener> listeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private @Nullable AppSession session; |
| |
| private @Nullable RProcess tool; |
| private @Nullable IWorkbenchPage workbenchPage; |
| |
| private @Nullable DataLoader variablesLoader; |
| private @Nullable volatile VariablesData variablesData; |
| |
| |
| public AppRunner(final AppControlLaunchConfig config) { |
| super(RUN_TASK_ID, NLS.bind("Run R App ''{0}''", |
| config.getAppFolder().getFullPath().toString() )); |
| |
| this.config= config; |
| initVars(); |
| } |
| |
| |
| @Override |
| public IResource getResource() { |
| return this.config.getAppFolder(); |
| } |
| |
| |
| @Override |
| protected void run(final IRConsoleService r, |
| final ProgressMonitor m) throws StatusException { |
| final AppSession session; |
| AppRCommandHandler listener= null; |
| try { |
| synchronized (this) { |
| this.tool= (RProcess) r.getTool(); |
| session= new AppSession(this.tool); |
| this.session= session; |
| } |
| |
| session.init(m); |
| |
| listener= AppRCommandHandler.connect(this, r, m); |
| |
| r.briefAboutToChange(); |
| |
| r.submitToConsole(session.getStartCode(), m); |
| } |
| finally { |
| r.briefChanged(IRConsoleService.AUTO_CHANGE); |
| |
| onAppStopped(null); |
| |
| if (listener != null) { |
| listener.disconnect(this); |
| } |
| } |
| } |
| |
| protected void onAppStarted(final String url, final String typeId) { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| if (session == null) { |
| return; |
| } |
| } |
| |
| final boolean isRunning= session.onStarted(url); |
| updateVarsOnStarted(); |
| |
| if (isRunning) { |
| AppRegistry.getInstance().onAppStarted(session.getIdUrl(), this); |
| |
| if (this.config.getViewerId() != null) { |
| showViewer(session, this.config.getViewerId()); |
| } |
| if (this.config.getVariablesViewAction() != 0 && this.variablesData != null) { |
| showVariablesView(session, this.config.getVariablesViewAction()); |
| } |
| refreshVariables(); |
| } |
| } |
| |
| protected void onAppStopped(final @Nullable String url) { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| if (session == null) { |
| return; |
| } |
| |
| this.session= null; |
| } |
| |
| session.onAppStop(); |
| |
| if (session.getIdUrl() != null) { |
| AppRegistry.getInstance().onAppStopped(session.getIdUrl(), this); |
| } |
| |
| updateVarsOnStopped(); |
| } |
| |
| |
| @Override |
| public @Nullable RProcess getTool() { |
| return this.tool; |
| } |
| |
| protected IWorkbenchPage getWorkbenchPage() { |
| IWorkbenchPage page= this.workbenchPage; |
| if (page == null) { |
| page= this.config.getWorkbenchPage(); |
| } |
| if (page != null && page.getWorkbenchWindow().getActivePage() == page) { |
| return page; |
| } |
| page= UIAccess.getActiveWorkbenchPage(true); |
| this.workbenchPage= page; |
| return page; |
| } |
| |
| @Override |
| public boolean isRunning() { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| |
| return (session != null && session.isRunning()); |
| } |
| } |
| |
| long getStartedTimestamp() { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| |
| return (session != null && session.isRunning()) ? session.startedTimestamp : Long.MIN_VALUE; |
| } |
| } |
| |
| @Override |
| public void startApp(final IWorkbenchPage page) throws CoreException { |
| final RProcess tool= fetchRProcess(page); |
| |
| final Status status= tryStart(tool); |
| if (status.getSeverity() < Status.ERROR) { |
| return; |
| } |
| |
| throw new CoreException(StatusUtils.convert(status)); |
| } |
| |
| @Override |
| public boolean canRestartApp() { // (!isRunning() || canStopApp()) |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| if (session == null || !session.isRunning()) { |
| return true; |
| } |
| } |
| |
| return (this.config.getStopCode() != null); |
| } |
| |
| @Override |
| public void restartApp(final IWorkbenchPage page) throws CoreException { |
| final AppSession session; |
| RProcess tool; |
| Queue.Section queueSection; |
| synchronized (this) { |
| tool= this.tool; |
| session= this.session; |
| |
| queueSection= (session != null && session.isRunning()) ? session.queueSection : null; |
| } |
| |
| final AppRunner runner= new AppRunner(this.config); |
| runner.workbenchPage= this.workbenchPage; |
| |
| Status status= null; |
| if (tool != null) { |
| if (queueSection != null && queueSection != tool.getQueue().getTopLevelSection()) { |
| final Status status0= runner.tryRestart(tool, queueSection); |
| if (status0.getSeverity() < Status.ERROR) { |
| return; |
| } |
| } |
| { final Status status0= runner.tryRestart(tool, tool.getQueue().getTopLevelSection()); |
| if (status0.getSeverity() < Status.ERROR) { |
| return; |
| } |
| if (status == null) { |
| status= status0; |
| } |
| } |
| } |
| |
| { tool= fetchRProcess(page); |
| final Status status0= runner.tryRestart(tool, tool.getQueue().getTopLevelSection()); |
| if (status0.getSeverity() < Status.ERROR) { |
| return; |
| } |
| if (status == null) { |
| status= status0; |
| } |
| } |
| |
| throw new CoreException(StatusUtils.convert(status)); |
| } |
| |
| private Status tryStart(final RProcess tool) { |
| final Queue queue= tool.getQueue(); |
| final Status status= queue.add(this); |
| |
| if (status.getSeverity() < Status.ERROR) { |
| stopBlocking(tool, this.config.getStopBlocking()); |
| } |
| |
| return status; |
| } |
| |
| private Status tryRestart(final RProcess tool, final Queue.Section queueSection) { |
| final Queue queue= tool.getQueue(); |
| final Status status= queue.add(this, queueSection, Queue.IF_ABSENT); |
| |
| if (status.getSeverity() < Status.ERROR) { |
| final AppRunner runner= (AppRunner) ((RunnableStatus) status).getRunnable(); |
| |
| runner.stopBlocking(tool, this.config.getStopBlocking() | 2); |
| } |
| |
| return status; |
| } |
| |
| private void stopBlocking(final RProcess tool, final int mode) { |
| if (mode == 0) { |
| return; |
| } |
| final ToolController controller= tool.getController(); |
| if (controller != null) { |
| final ToolRunnable currentRunnable= controller.getCurrentRunnable(); |
| if (currentRunnable != this && currentRunnable instanceof AppRunner) { |
| final AppRunner runner= (AppRunner) currentRunnable; |
| if ((mode & 1) != 0 |
| || ((mode & 2) != 0 && runner.config == this.config) ) { |
| runner.stopApp(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean canStopApp() { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| if (session == null || !session.isRunning()) { |
| return false; |
| } |
| } |
| |
| return (this.config.getStopCode() != null); |
| } |
| |
| @Override |
| public void stopApp() { |
| final AppSession session; |
| synchronized (this) { |
| session= this.session; |
| if (session == null) { |
| return; |
| } |
| } |
| |
| if (this.config.getStopCode() != null) { |
| session.getTool().getQueue().addHot(new StopRunnable()); |
| } |
| } |
| |
| |
| private void showViewer(final AppSession session, final String viewerId) { |
| if (session.getLocalUrl() == null) { |
| onError(IStatus.ERROR, "Cannot open viewer to show the R app: URL is missing.", null, |
| StatusManager.SHOW ); |
| return; |
| } |
| |
| UIAccess.getDisplay().asyncExec(() -> { |
| synchronized (AppRunner.this) { |
| if (session != this.session || !session.isRunning()) { |
| return; |
| } |
| } |
| try { |
| switch (viewerId) { |
| case AppControlConfigs.WORKBENCH_EXTERNAL_BROWSER_ID: |
| openExternalBrowser(session); |
| return; |
| case AppControlConfigs.WORKBENCH_VIEW_BROWSER_ID: |
| openViewBrowser(getWorkbenchPage(), session); |
| return; |
| default: |
| throw new UnsupportedOperationException("viewerId= " + viewerId); //$NON-NLS-1$ |
| } |
| } |
| catch (final Exception e) { |
| onError(IStatus.ERROR, Messages.Operation_Viewer_error_Run_message, e, |
| StatusManager.LOG | StatusManager.SHOW ); |
| } |
| }); |
| } |
| |
| private void openExternalBrowser(final AppSession session) throws PartInitException { |
| final IWorkbenchBrowserSupport browserSupport= PlatformUI.getWorkbench().getBrowserSupport(); |
| final IWebBrowser webBrowser= browserSupport.getExternalBrowser(); |
| webBrowser.openURL(session.getLocalUrl()); |
| } |
| |
| private AppBrowserView getView(final IWorkbenchPage page) throws PartInitException { |
| return (AppBrowserView) page.showView(AppBrowserView.VIEW_ID, null, |
| IWorkbenchPage.VIEW_VISIBLE ); |
| } |
| |
| private void openViewBrowser(final IWorkbenchPage page, final AppSession session) throws PartInitException { |
| final AppBrowserView view= getView(page); |
| AppBrowserSession viewSession= (AppBrowserSession) BrowserSession.findSessionById( |
| view.getSessions(), session.getIdUrl() ); |
| if (viewSession == null) { |
| viewSession= new AppBrowserSession(session.getIdUrl()); |
| } |
| view.openUrl(session.getLocalUrl().toExternalForm(), viewSession); |
| } |
| |
| |
| private void showVariablesView(final AppSession session, final int viewActionMode) { |
| UIAccess.getDisplay().asyncExec(() -> { |
| synchronized (AppRunner.this) { |
| if (session != this.session || !session.isRunning()) { |
| return; |
| } |
| } |
| try { |
| final IWorkbenchPage page= getWorkbenchPage(); |
| final IViewReference viewRef= page.findViewReference(AppVarView.VIEW_ID); |
| if (viewRef != null) { |
| if (viewRef.isFastView()) { |
| return; |
| } |
| final IViewPart view= viewRef.getView(false); |
| if (view != null && page.isPartVisible(view)) { |
| return; |
| } |
| } |
| final AppVarView view= (AppVarView) page.showView(AppVarView.VIEW_ID, null, |
| viewActionMode ); |
| view.setShownByLauncher(this); |
| } |
| catch (final PartInitException e) { |
| onError(IStatus.ERROR, Messages.Operation_Variables_error_Run_message, e); |
| } |
| }); |
| } |
| |
| |
| @Override |
| public void addListener(final Listener listener) { |
| boolean refresh; |
| synchronized (this.listeners) { |
| refresh= (this.listeners.isEmpty() && this.variablesLoader == null); |
| |
| this.listeners.add(listener); |
| } |
| if (refresh) { |
| refreshVariables(); |
| } |
| } |
| |
| @Override |
| public void removeListener(final Listener listener) { |
| this.listeners.remove(listener); |
| } |
| |
| @Override |
| public @Nullable VariablesData getVariables() { |
| return this.variablesData; |
| } |
| |
| private void initVars() { |
| final String code= this.config.getVariablesCode(); |
| if (code == null) { |
| return; |
| } |
| this.variablesData= new VariablesData(code, NOT_RUNNING_DATA_STATUS); |
| } |
| |
| private void updateVarsOnStarted() { |
| final VariablesData data= this.variablesData; |
| if (data == null || data.getStatus() != NOT_RUNNING_DATA_STATUS) { |
| return; |
| } |
| setData(new VariablesData(data.getExpression(), NOT_LOADED_DATA_STATUS)); |
| } |
| |
| @Override |
| public void refreshVariables() { |
| final VariablesData data= this.variablesData; |
| if (data == null) { |
| return; |
| } |
| DataLoader loader; |
| synchronized (this.listeners) { |
| loader= this.variablesLoader; |
| if (loader == null) { |
| if (this.listeners.isEmpty() || !isRunning()) { |
| return; |
| } |
| loader= new DataLoader(this, data.getExpression()); |
| this.variablesLoader= loader; |
| } |
| } |
| loader.schedule(); |
| } |
| |
| private void updateVarsOnStopped() { |
| final VariablesData data= this.variablesData; |
| if (data == null) { |
| return; |
| } |
| DataLoader loader; |
| synchronized (this.listeners) { |
| loader= this.variablesLoader; |
| this.variablesLoader= null; |
| } |
| if (loader != null) { |
| loader.stop(); |
| } |
| setData(new VariablesData(data.getExpression(), NOT_RUNNING_DATA_STATUS)); |
| } |
| |
| void setData(final VariablesData variables) { |
| this.variablesData= variables; |
| |
| final AppEvent event= new AppEvent(this); |
| for (final Listener listener : this.listeners) { |
| listener.onVariablesChanged(event); |
| } |
| } |
| |
| private void onError(final int severity, final String message, final @Nullable Throwable e, |
| final int style) { |
| StatusManager.getManager().handle(new org.eclipse.core.runtime.Status( |
| severity, RAppUIPlugin.BUNDLE_ID, message, e ), |
| style ); |
| } |
| |
| private void onError(final int severity, final String message, final @Nullable Throwable e) { |
| onError(severity, message, e, StatusManager.LOG); |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| return this.config.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(final @Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj instanceof AppRunner) { |
| return (this.config == ((AppRunner) obj).config); |
| } |
| return false; |
| } |
| |
| } |