Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUwe Stieber2012-08-12 12:59:08 -0400
committerUwe Stieber2012-08-12 12:59:08 -0400
commit8e75b57e4a49282013a4d70d8844373bbe623c96 (patch)
treeefa7351f6390519ef1d2725f77f6e355255dbb5a
parent87db079b512afec89f418e1b2797d9da2127e112 (diff)
downloadorg.eclipse.tcf-8e75b57e4a49282013a4d70d8844373bbe623c96.tar.gz
org.eclipse.tcf-8e75b57e4a49282013a4d70d8844373bbe623c96.tar.xz
org.eclipse.tcf-8e75b57e4a49282013a4d70d8844373bbe623c96.zip
Target Explorer: Bug 378625 - [Terminals] Cannot open two tabs to the same host in one view
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.runtime.services/src/org/eclipse/tcf/te/runtime/services/interfaces/constants/ITerminalsConnectorConstants.java5
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java2036
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java1
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java102
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.serial/src/org/eclipse/tcf/te/ui/terminals/serial/launcher/SerialLauncherDelegate.java3
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.ssh/src/org/eclipse/tcf/te/ui/terminals/ssh/launcher/SshLauncherDelegate.java9
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.telnet/src/org/eclipse/tcf/te/ui/terminals/telnet/launcher/TelnetLauncherDelegate.java11
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/interfaces/ImageConsts.java2
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/manager/ConsoleManager.java30
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/services/TerminalService.java8
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderManager.java6
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderMenuHandler.java8
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderToolbarHandler.java8
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/view/TerminalsView.java2
14 files changed, 1151 insertions, 1080 deletions
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.runtime.services/src/org/eclipse/tcf/te/runtime/services/interfaces/constants/ITerminalsConnectorConstants.java b/target_explorer/plugins/org.eclipse.tcf.te.runtime.services/src/org/eclipse/tcf/te/runtime/services/interfaces/constants/ITerminalsConnectorConstants.java
index 7a84a6c23..b68928f23 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.runtime.services/src/org/eclipse/tcf/te/runtime/services/interfaces/constants/ITerminalsConnectorConstants.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.runtime.services/src/org/eclipse/tcf/te/runtime/services/interfaces/constants/ITerminalsConnectorConstants.java
@@ -31,6 +31,11 @@ public interface ITerminalsConnectorConstants {
public static final String PROP_DATA = "data"; //$NON-NLS-1$
/**
+ * Property: Flag to force a new terminal tab.
+ */
+ public static final String PROP_FORCE_NEW = "forceNew"; //$NON-NLS-1$
+
+ /**
* Property: Terminals connector type id.
*/
public static final String PROP_CONNECTOR_TYPE_ID = "connector.type.id"; //$NON-NLS-1$
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
index ff904b9b5..8d01d1a13 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
@@ -1,1018 +1,1018 @@
-/*******************************************************************************
- * Copyright (c) 2011, 2012 Wind River Systems, Inc. and others. All rights reserved.
- * This program and the accompanying materials are made available under the terms
- * of the Eclipse Public License v1.0 which accompanies this distribution, and is
- * available at http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Wind River Systems - initial API and implementation
- *******************************************************************************/
-package org.eclipse.tcf.te.tcf.processes.core.launcher;
-
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.core.runtime.Assert;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.PlatformObject;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.tcf.protocol.IChannel;
-import org.eclipse.tcf.protocol.IChannel.IChannelListener;
-import org.eclipse.tcf.protocol.IPeer;
-import org.eclipse.tcf.protocol.IToken;
-import org.eclipse.tcf.protocol.Protocol;
-import org.eclipse.tcf.services.IProcesses;
-import org.eclipse.tcf.services.IProcesses.ProcessContext;
-import org.eclipse.tcf.services.IStreams;
-import org.eclipse.tcf.te.core.async.AsyncCallbackCollector;
-import org.eclipse.tcf.te.runtime.callback.Callback;
-import org.eclipse.tcf.te.runtime.events.DisposedEvent;
-import org.eclipse.tcf.te.runtime.events.EventManager;
-import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
-import org.eclipse.tcf.te.runtime.interfaces.events.IEventListener;
-import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
-import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
-import org.eclipse.tcf.te.runtime.services.ServiceManager;
-import org.eclipse.tcf.te.runtime.services.interfaces.ITerminalService;
-import org.eclipse.tcf.te.runtime.services.interfaces.constants.ITerminalsConnectorConstants;
-import org.eclipse.tcf.te.tcf.core.Tcf;
-import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate;
-import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
-import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
-import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
-import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessContextAwareListener;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessStreamsProxy;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.tracing.ITraceIds;
-import org.eclipse.tcf.te.tcf.processes.core.nls.Messages;
-
-/**
- * Remote process launcher.
- * <p>
- * The process launcher is implemented fully asynchronous.
- */
-public class ProcessLauncher extends PlatformObject implements IProcessLauncher {
- // The channel instance
- /* default */ IChannel channel = null;
- // The process properties instance
- /* default */ IPropertiesContainer properties;
-
- // The processes service instance
- /* default */ IProcesses svcProcesses;
- // The streams service instance
- /* default */ IStreams svcStreams;
- // The remote process context
- /* default */ IProcesses.ProcessContext processContext;
-
- // The callback instance
- private ICallback callback;
-
- // The streams listener instance
- private IStreams.StreamsListener streamsListener = null;
- // The process listener instance
- private IProcesses.ProcessesListener processesListener = null;
- // The event listener instance
- private IEventListener eventListener = null;
-
- // The streams proxy instance
- private IProcessStreamsProxy streamsProxy = null;
-
- /**
- * Constructor.
- */
- public ProcessLauncher() {
- super();
- }
-
- /**
- * Constructor.
- */
- public ProcessLauncher(IProcessStreamsProxy streamsProxy) {
- super();
- this.streamsProxy = streamsProxy;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#dispose()
- */
- @Override
- public void dispose() {
- // Unlink the process context
- processContext = null;
-
- // Store a final reference to the channel instance
- final IChannel finChannel = channel;
-
- // Remove the notification listener
- if (eventListener != null) {
- EventManager.getInstance().removeEventListener(eventListener);
- eventListener = null;
- }
-
- // Create the callback collector
- final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
- @Override
- protected void internalDone(Object caller, IStatus status) {
- Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
- // Close the channel as all disposal is done
- if (finChannel != null) {
- Tcf.getChannelManager().closeChannel(finChannel);
- }
- }
- }, new CallbackInvocationDelegate());
-
- if (streamsListener != null) {
- // Dispose the streams listener
- if (streamsListener instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- }
- streamsListener = null;
- }
-
- // Dispose the processes listener if created
- if (processesListener != null) {
- // Dispose the processes listener
- if (processesListener instanceof ProcessProcessesListener) {
- ((ProcessProcessesListener)processesListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- }
- processesListener = null;
- // Remove the processes listener from the processes service
- getSvcProcesses().removeListener(processesListener);
- }
-
- // Dispose the streams proxy if created
- if (streamsProxy != null) {
- streamsProxy.dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- streamsProxy = null;
- }
- // Mark the collector initialization as done
- collector.initDone();
-
- // Dissociate the channel
- channel = null;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#terminate()
- */
- @Override
- public void terminate() {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- if (processContext != null && processContext.canTerminate()) {
- // Try to terminate the process the usual way first (sending SIGTERM)
- processContext.terminate(new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- onTerminateDone(processContext, error);
- }
- });
- }
-
- }
- };
-
- if (Protocol.isDispatchThread()) {
- runnable.run();
- }
- else {
- Protocol.invokeAndWait(runnable);
- }
- }
-
- /**
- * Check if the process context really died after sending SIGTERM.
- * <p>
- * Called from {@link #terminate()}.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @param error The exception in case {@link #terminate()} returned with an error or <code>null</code>.
- */
- protected void onTerminateDone(IProcesses.ProcessContext context, Exception error) {
- Assert.isNotNull(context);
-
- // If the terminate of the remote process context failed, give a warning to the user
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processTerminateFailed, context.getName());
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- // No error from terminate, this does not mean that the process went down
- // really -> SIGTERM might have been ignored from the process!
- else {
- final IProcesses.ProcessContext finContext = context;
- // Let's see if we can still get information about the context
- getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
- @Override
- public void doneGetContext(IToken token, Exception error, ProcessContext context) {
- // In case there is no error and we do get back an process context,
- // the process must be still running, having ignored the SIGTERM.
- if (error == null && context != null && context.getID().equals(finContext.getID())) {
- // Let's send a SIGHUP next.
- getSvcProcesses().signal(context.getID(), 15, new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- onSignalSIGHUPDone(finContext, error);
- }
- });
- }
- }
- });
- }
- }
-
- /**
- * Check if the process context died after sending SIGHUP.
- * <p>
- * Called from {@link #onTerminateDone(IProcesses.ProcessContext, Exception)}.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @param error The exception in case sending the signal returned with an error or <code>null</code>.
- */
- protected void onSignalSIGHUPDone(IProcesses.ProcessContext context, Exception error) {
- Assert.isNotNull(context);
-
- // If the terminate of the remote process context failed, give a warning to the user
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGHUP(15)", context.getName()); //$NON-NLS-1$
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- // No error from terminate, this does not mean that the process went down
- // really -> SIGTERM might have been ignored from the process!
- else {
- final IProcesses.ProcessContext finContext = context;
- // Let's see if we can still get information about the context
- getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
- @Override
- public void doneGetContext(IToken token, Exception error, ProcessContext context) {
- // In case there is no error and we do get back an process context,
- // the process must be still running, having ignored the SIGHUP.
- if (error == null && context != null && context.getID().equals(finContext.getID())) {
- // Finally send a SIGKILL.
- getSvcProcesses().signal(context.getID(), 9, new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGKILL(15)", finContext.getName()); //$NON-NLS-1$
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- }
- });
- }
- }
- });
- }
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
- */
- @Override
- public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) {
- Assert.isNotNull(peer);
- Assert.isNotNull(properties);
-
- // Normalize the callback
- if (callback == null) {
- this.callback = new Callback() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
- */
- @Override
- public void internalDone(Object caller, IStatus status) {
- }
- };
- }
- else {
- this.callback = callback;
- }
-
- // Remember the process properties
- this.properties = properties;
-
- // Open a dedicated channel to the given peer
- Map<String, Boolean> flags = new HashMap<String, Boolean>();
- flags.put(IChannelManager.FLAG_FORCE_NEW, Boolean.TRUE);
- if (channel != null && channel.getState() == IChannel.STATE_OPEN) {
- onChannelOpenDone(peer);
- }
- else {
- Tcf.getChannelManager().openChannel(peer, flags, new IChannelManager.DoneOpenChannel() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel)
- */
- @Override
- public void doneOpenChannel(Throwable error, IChannel channel) {
- if (error == null) {
- ProcessLauncher.this.channel = channel;
- onChannelOpenDone(peer);
- } else {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
- error);
- invokeCallback(status, null);
- }
- }
- });
- }
- }
-
- protected void onChannelOpenDone(final IPeer peer) {
- Protocol.invokeAndWait(new Runnable() {
- @Override
- public void run() {
- // Attach a channel listener so we can dispose ourself if the channel
- // is closed from the remote side.
- channel.addChannelListener(new IChannelListener() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened()
- */
- @Override
- public void onChannelOpened() {
- }
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable)
- */
- @Override
- public void onChannelClosed(Throwable error) {
- if (error != null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
- error);
- invokeCallback(status, null);
- }
- }
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int)
- */
- @Override
- public void congestionLevel(int level) {
- }
- });
-
-
- // Check if the channel is in connected state
- if (channel.getState() != IChannel.STATE_OPEN) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- Messages.ProcessLauncher_error_channelNotConnected,
- new IllegalStateException());
- invokeCallback(status, null);
- return;
- }
-
- // Do some very basic sanity checking on the process properties
- if (properties.getStringProperty(PROP_PROCESS_PATH) == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- Messages.ProcessLauncher_error_missingProcessPath,
- new IllegalArgumentException(PROP_PROCESS_PATH));
- invokeCallback(status, null);
- return;
- }
-
- // Get the process and streams services
- svcProcesses = channel.getRemoteService(IProcesses.class);
- if (svcProcesses == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IProcesses.class.getName()),
- null);
-
- invokeCallback(status, null);
- return;
- }
-
- svcStreams = channel.getRemoteService(IStreams.class);
- if (svcStreams == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IStreams.class.getName()),
- null);
- invokeCallback(status, null);
- return;
- }
-
- // Execute the launch now
- executeLaunch();
- }
- });
- }
-
- /**
- * Executes the launch of the remote process.
- */
- protected void executeLaunch() {
- // Get the process properties container
- final IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // If a console should be associated, a streams listener needs to be created
- if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)
- || properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
- // Create the streams listener
- streamsListener = createStreamsListener();
- // If available, we need to subscribe to the streams.
- if (streamsListener != null) {
- getSvcStreams().subscribe(IProcesses.NAME, streamsListener, new IStreams.DoneSubscribe() {
- @Override
- public void doneSubscribe(IToken token, Exception error) {
- // In case the subscribe to the stream fails, we pass on
- // the error to the user and stop the launch
- if (error != null) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, Messages.ProcessLauncher_cause_subscribeFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Initialize the console or output file
- onSubscribeStreamsDone();
- }
- }
- });
- } else {
- // No streams to attach to -> go directly to the process launch
- onAttachStreamsDone();
- }
- } else {
- // No streams to attach to -> go directly to the process launch
- onAttachStreamsDone();
- }
- }
-
- /**
- * Initialize and attach the output console and/or the output file.
- * <p>
- * Called from {@link IStreams#subscribe(String, org.eclipse.tcf.services.IStreams.StreamsListener, org.eclipse.tcf.services.IStreams.DoneSubscribe)}.
- */
- protected void onSubscribeStreamsDone() {
- // Get the process properties container
- IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // The streams got subscribed, check if we shall attach the console
- if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)) {
- // If no specific streams proxy is set, the output redirection will default
- // to the standard terminals console view
- if (streamsProxy == null) {
- // Register the notification listener to listen to the console disposal
- eventListener = new ProcessLauncherEventListener(this);
- EventManager.getInstance().addEventListener(eventListener, DisposedEvent.class);
-
- // Get the terminal service
- ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
- // If not available, we cannot fulfill this request
- if (terminal != null) {
- // Create the terminal streams settings
- PropertiesContainer props = new PropertiesContainer();
- props.setProperty(ITerminalsConnectorConstants.PROP_CONNECTOR_TYPE_ID, "org.eclipse.tcf.te.ui.terminals.type.streams"); //$NON-NLS-1$
- props.setProperty(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tcf.te.ui.terminals.TerminalsView"); //$NON-NLS-1$
- // Set the terminal tab title
- String terminalTitle = getTerminalTitle();
- if (terminalTitle != null) {
- props.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
- }
-
- // Create and store the streams which will be connected to the terminals stdin
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
- // Create and store the streams the terminal will see as stdout
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
- // Create and store the streams the terminal will see as stderr
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
-
- // Copy the terminal properties
- props.setProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO));
- props.setProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR, properties.getStringProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR));
-
- // The custom data object is the process launcher itself
- props.setProperty(ITerminalsConnectorConstants.PROP_DATA, this);
-
- // Open the console
- terminal.openConsole(props, null);
- }
- } else {
- // Create and connect the streams which will be connected to the terminals stdin
- streamsProxy.connectInputStreamMonitor(connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
- // Create and store the streams the terminal will see as stdout
- streamsProxy.connectOutputStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
- // Create and store the streams the terminal will see as stderr
- streamsProxy.connectErrorStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
- }
- }
-
- // The streams got subscribed, check if we shall configure the output redirection to a file
- if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
- // Get the file name where to redirect the process output to
- String filename = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE);
- try {
- // Create the receiver instance. If the file already exist, we
- // overwrite the file content.
- StreamsDataReceiver receiver = new StreamsDataReceiver(new BufferedWriter(new FileWriter(filename)),
- new String[] { IProcesses.PROP_STDOUT_ID, IProcesses.PROP_STDERR_ID });
- // Register the receiver to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
- }
- } catch (IOException e) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- e.getLocalizedMessage() != null ? e.getLocalizedMessage() : Messages.ProcessLauncher_cause_ioexception);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e);
- invokeCallback(status, null);
- }
- }
-
- // Launch the process
- onAttachStreamsDone();
- }
-
- /**
- * Returns the terminal title string.
- * <p>
- * The default implementation constructs a title like &quot;<process> (Start time) [channel name]&quot;.
- *
- * @return The terminal title string or <code>null</code>.
- */
- protected String getTerminalTitle() {
- if (properties == null) {
- return null;
- }
-
- StringBuilder title = new StringBuilder();
-
- IPath processPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH));
- IPath monitoredProcessPath = null;
- if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH) != null) {
- monitoredProcessPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH));
- }
-
- // In case, we do have a monitored process path here, we construct the title
- // as <monitor_app_basename>: <monitored_app>"
- if (monitoredProcessPath != null) {
- title.append(processPath.lastSegment());
- title.append(": "); //$NON-NLS-1$
- processPath = monitoredProcessPath;
- }
-
- // Avoid very long terminal title's by shortening the path if it has more than 3 segments
- if (processPath.segmentCount() > 3) {
- title.append(".../"); //$NON-NLS-1$
- title.append(processPath.lastSegment());
- } else {
- title.append(processPath.toString());
- }
-
- // Get the peer name
- final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(IProcessLauncher.PROP_CONNECTION_NAME));
- if (peerName.get() == null) {
- // Query the peer from the open channel
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- if (channel != null) {
- peerName.set(channel.getRemotePeer().getName());
- }
- }
- };
-
- if (Protocol.isDispatchThread()) {
- runnable.run();
- }
- else {
- Protocol.invokeAndWait(runnable);
- }
- }
-
- if (peerName.get() != null) {
- title.append(" [").append(peerName.get()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
- String date = format.format(new Date(System.currentTimeMillis()));
- title.append(" (").append(date).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
-
- return title.toString();
- }
-
- /**
- * Connects the given stream id's to a local {@link InputStream} instance.
- *
- * @param streamsListener The streams listener. Must not be <code>null</code>.
- * @param streamIds The stream id's. Must not be <code>null</code>.
- *
- * @return The local input stream instance or <code>null</code>.
- */
- protected InputStream connectRemoteInputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
- Assert.isNotNull(streamsListener);
- Assert.isNotNull(streamIds);
-
- InputStream stream = null;
-
- // Create the output stream receiving the data from remote
- PipedOutputStream remoteStreamDataReceiverStream = new PipedOutputStream();
- // Create the piped input stream instance
- try { stream = new PipedInputStream(remoteStreamDataReceiverStream); } catch (IOException e) { /* ignored on purpose */ }
-
- // If the input stream creation succeeded, connect the data receiver
- if (stream != null) {
- StreamsDataReceiver receiver = new StreamsDataReceiver(new OutputStreamWriter(remoteStreamDataReceiverStream), streamIds);
- // Register the data receiver to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
- }
- }
-
- return stream;
- }
-
- /**
- * Connects the given stream id's to a local {@link OutputStream} instance.
- *
- * @param streamsListener The streams listener. Must not be <code>null</code>.
- * @param streamIds The stream id's. Must not be <code>null</code>.
- *
- * @return The local output stream instance or <code>null</code>.
- */
- protected OutputStream connectRemoteOutputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
- Assert.isNotNull(streamsListener);
- Assert.isNotNull(streamIds);
-
- PipedInputStream inStream = null;
-
- // Create the output stream receiving the data from local
- PipedOutputStream stream = new PipedOutputStream();
- // Create the piped input stream instance
- try { inStream = new PipedInputStream(stream); } catch (IOException e) { stream = null; }
-
- // If the stream creation succeeded, connect the data provider
- if (stream != null && inStream != null) {
- StreamsDataProvider provider = new StreamsDataProvider(new InputStreamReader(inStream), streamIds);
- // Register the data provider to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).setDataProvider(provider);
- }
- }
-
- return stream;
- }
-
- /**
- * Queries the initial new process environment from remote.
- */
- protected void onAttachStreamsDone() {
- // Query the default environment for a new process
- getSvcProcesses().getEnvironment(new IProcesses.DoneGetEnvironment() {
- @Override
- public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) {
- if (error != null) {
- // Construct the error message to show to the user
- String message = Messages.ProcessLauncher_error_getEnvironmentFailed;
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Initiate the process launch
- onGetEnvironmentDone(environment);
- }
- }
- });
- }
-
- /**
- * Initiate the process launch.
- * <p>
- * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}.
- */
- protected void onGetEnvironmentDone(final Map<String, String> environment) {
- // Get the process properties container
- final IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // Create the process listener
- processesListener = createProcessesListener();
- if (processesListener != null) {
- getSvcProcesses().addListener(processesListener);
- }
-
- // Get the process attributes
- String processPath = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH);
-
- String[] processArgs = (String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS);
- // Assure that the first argument is the process path itself
- if (!(processArgs != null && processArgs.length > 0 && processPath.equals(processArgs[0]))) {
- // Prepend the process path to the list of arguments
- List<String> args = processArgs != null ? new ArrayList<String>(Arrays.asList(processArgs)) : new ArrayList<String>();
- args.add(0, processPath);
- processArgs = args.toArray(new String[args.size()]);
- }
-
- String processCWD = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_CWD);
- // If the process working directory is not explicitly set, default to the process path directory
- if (processCWD == null || "".equals(processCWD.trim())) { //$NON-NLS-1$
- processCWD = new Path(processPath).removeLastSegments(1).toString();
- }
-
- // Merge the initial process environment and the desired process environment
- Map<String, String> processEnv = new HashMap<String, String>(environment);
- Map<String, String> processEnvDiff = (Map<String, String>)properties.getProperty(IProcessLauncher.PROP_PROCESS_ENV);
- if (processEnvDiff != null && !processEnvDiff.isEmpty()) {
- processEnv.putAll(processEnvDiff);
- }
- // Assure that the TERM variable is set to "ansi"
- processEnv.put("TERM", "ansi"); //$NON-NLS-1$ //$NON-NLS-2$
-
- boolean attach = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ATTACH);
-
- // Launch the remote process
- getSvcProcesses().start(processCWD, processPath, processArgs, processEnv, attach, new IProcesses.DoneStart() {
- @Override
- public void doneStart(IToken token, Exception error, ProcessContext process) {
- if (error != null) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Register the process context to the listener
- onProcessLaunchDone(process);
- }
- }
- });
- }
-
- /**
- * Register the process context to the listeners.
- *
- * @param process The process context or <code>null</code>.
- */
- protected void onProcessLaunchDone(ProcessContext process) {
- // Register the process context with the listeners
- if (process != null) {
- if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_PROCESS_LAUNCHER)) {
- CoreBundleActivator.getTraceHandler().trace("Process context created: id='" + process.getID() + "', name='" + process.getName() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- 0, ITraceIds.TRACE_PROCESS_LAUNCHER, IStatus.INFO, getClass());
- }
-
- // Remember the process context
- processContext = process;
-
- // Push the process context to the listeners
- if (getStreamsListener() instanceof IProcessContextAwareListener) {
- ((IProcessContextAwareListener)getStreamsListener()).setProcessContext(process);
- }
- if (getProcessesListener() instanceof IProcessContextAwareListener) {
- ((IProcessContextAwareListener)getProcessesListener()).setProcessContext(process);
- }
-
- // Send a notification
- ProcessStateChangeEvent event = createRemoteProcessStateChangeEvent(process);
- if (event != null) {
- EventManager.getInstance().fireEvent(event);
- }
- }
-
- // Invoke the callback to signal that we are done
- invokeCallback(Status.OK_STATUS, process);
- }
-
- /**
- * Creates a new remote process state change event instance.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @return The event instance or <code>null</code>.
- */
- protected ProcessStateChangeEvent createRemoteProcessStateChangeEvent(IProcesses.ProcessContext context) {
- Assert.isNotNull(context);
- return new ProcessStateChangeEvent(context, ProcessStateChangeEvent.EVENT_PROCESS_CREATED, Boolean.FALSE, Boolean.TRUE, -1);
- }
-
- /**
- * Invoke the callback with the given parameters. If the given status severity
- * is {@link IStatus#ERROR}, the process launcher object is disposed automatically.
- *
- * @param status The status. Must not be <code>null</code>.
- * @param result The result object or <code>null</code>.
- */
- protected void invokeCallback(IStatus status, Object result) {
- // Dispose the process launcher if we report an error
- if (status.getSeverity() == IStatus.ERROR) {
- dispose();
- }
-
- // Invoke the callback
- ICallback callback = getCallback();
- if (callback != null) {
- callback.setResult(result);
- callback.done(this, status);
- }
- }
-
- /**
- * Returns the channel instance.
- *
- * @return The channel instance or <code>null</code> if none.
- */
- public final IChannel getChannel() {
- return channel;
- }
-
- /**
- * Returns the process properties container.
- *
- * @return The process properties container or <code>null</code> if none.
- */
- public final IPropertiesContainer getProperties() {
- return properties;
- }
-
- /**
- * Returns the processes service instance.
- *
- * @return The processes service instance or <code>null</code> if none.
- */
- public final IProcesses getSvcProcesses() {
- return svcProcesses;
- }
-
- /**
- * Returns the streams service instance.
- *
- * @return The streams service instance or <code>null</code> if none.
- */
- public final IStreams getSvcStreams() {
- return svcStreams;
- }
-
- /**
- * Returns the callback instance.
- *
- * @return The callback instance or <code>null</code> if none.
- */
- protected final ICallback getCallback() {
- return callback;
- }
-
- /**
- * Create the streams listener instance.
- *
- * @return The streams listener instance or <code>null</code> if none.
- */
- protected IStreams.StreamsListener createStreamsListener() {
- return new ProcessStreamsListener(this);
- }
-
- /**
- * Returns the streams listener instance.
- *
- * @return The streams listener instance or <code>null</code>.
- */
- protected final IStreams.StreamsListener getStreamsListener() {
- return streamsListener;
- }
-
- /**
- * Create the processes listener instance.
- *
- * @return The processes listener instance or <code>null</code> if none.
- */
- protected IProcesses.ProcessesListener createProcessesListener() {
- return new ProcessProcessesListener(this);
- }
-
- /**
- * Returns the processes listener instance.
- *
- * @return The processes listener instance or <code>null</code> if none.
- */
- protected final IProcesses.ProcessesListener getProcessesListener() {
- return processesListener;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class)
- */
- @Override
- public Object getAdapter(Class adapter) {
- if (adapter.isAssignableFrom(IProcesses.ProcessesListener.class)) {
- return processesListener;
- }
- else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) {
- return streamsListener;
- }
- else if (adapter.isAssignableFrom(IStreams.class)) {
- return svcStreams;
- }
- else if (adapter.isAssignableFrom(IProcesses.class)) {
- return svcProcesses;
- }
- else if (adapter.isAssignableFrom(IChannel.class)) {
- return channel;
- }
- else if (adapter.isAssignableFrom(IPropertiesContainer.class)) {
- return properties;
- }
- else if (adapter.isAssignableFrom(IProcesses.ProcessContext.class)) {
- return processContext;
- }
- else if (adapter.isAssignableFrom(this.getClass())) {
- return this;
- }
-
-
- return super.getAdapter(adapter);
- }
-
- /**
- * Makes a space separated string from the given array.
- *
- * @param array The string array or <code>null</code>.
- * @return The string.
- */
- String makeString(String[] array) {
- if (array == null) {
- return ""; //$NON-NLS-1$
- }
- StringBuilder result = new StringBuilder();
- for (String element : array) {
- if (result.length() > 0) {
- result.append(' ');
- }
- result.append(element);
- }
- return result.toString();
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 Wind River Systems, Inc. and others. All rights reserved.
+ * This program and the accompanying materials are made available under the terms
+ * of the Eclipse Public License v1.0 which accompanies this distribution, and is
+ * available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.processes.core.launcher;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IChannel.IChannelListener;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.protocol.Protocol;
+import org.eclipse.tcf.services.IProcesses;
+import org.eclipse.tcf.services.IProcesses.ProcessContext;
+import org.eclipse.tcf.services.IStreams;
+import org.eclipse.tcf.te.core.async.AsyncCallbackCollector;
+import org.eclipse.tcf.te.runtime.callback.Callback;
+import org.eclipse.tcf.te.runtime.events.DisposedEvent;
+import org.eclipse.tcf.te.runtime.events.EventManager;
+import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
+import org.eclipse.tcf.te.runtime.interfaces.events.IEventListener;
+import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
+import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
+import org.eclipse.tcf.te.runtime.services.ServiceManager;
+import org.eclipse.tcf.te.runtime.services.interfaces.ITerminalService;
+import org.eclipse.tcf.te.runtime.services.interfaces.constants.ITerminalsConnectorConstants;
+import org.eclipse.tcf.te.tcf.core.Tcf;
+import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
+import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessContextAwareListener;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessStreamsProxy;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.tracing.ITraceIds;
+import org.eclipse.tcf.te.tcf.processes.core.nls.Messages;
+
+/**
+ * Remote process launcher.
+ * <p>
+ * The process launcher is implemented fully asynchronous.
+ */
+public class ProcessLauncher extends PlatformObject implements IProcessLauncher {
+ // The channel instance
+ /* default */ IChannel channel = null;
+ // The process properties instance
+ /* default */ IPropertiesContainer properties;
+
+ // The processes service instance
+ /* default */ IProcesses svcProcesses;
+ // The streams service instance
+ /* default */ IStreams svcStreams;
+ // The remote process context
+ /* default */ IProcesses.ProcessContext processContext;
+
+ // The callback instance
+ private ICallback callback;
+
+ // The streams listener instance
+ private IStreams.StreamsListener streamsListener = null;
+ // The process listener instance
+ private IProcesses.ProcessesListener processesListener = null;
+ // The event listener instance
+ private IEventListener eventListener = null;
+
+ // The streams proxy instance
+ private IProcessStreamsProxy streamsProxy = null;
+
+ /**
+ * Constructor.
+ */
+ public ProcessLauncher() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ */
+ public ProcessLauncher(IProcessStreamsProxy streamsProxy) {
+ super();
+ this.streamsProxy = streamsProxy;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#dispose()
+ */
+ @Override
+ public void dispose() {
+ // Unlink the process context
+ processContext = null;
+
+ // Store a final reference to the channel instance
+ final IChannel finChannel = channel;
+
+ // Remove the notification listener
+ if (eventListener != null) {
+ EventManager.getInstance().removeEventListener(eventListener);
+ eventListener = null;
+ }
+
+ // Create the callback collector
+ final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
+ @Override
+ protected void internalDone(Object caller, IStatus status) {
+ Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
+ // Close the channel as all disposal is done
+ if (finChannel != null) {
+ Tcf.getChannelManager().closeChannel(finChannel);
+ }
+ }
+ }, new CallbackInvocationDelegate());
+
+ if (streamsListener != null) {
+ // Dispose the streams listener
+ if (streamsListener instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ streamsListener = null;
+ }
+
+ // Dispose the processes listener if created
+ if (processesListener != null) {
+ // Dispose the processes listener
+ if (processesListener instanceof ProcessProcessesListener) {
+ ((ProcessProcessesListener)processesListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ processesListener = null;
+ // Remove the processes listener from the processes service
+ getSvcProcesses().removeListener(processesListener);
+ }
+
+ // Dispose the streams proxy if created
+ if (streamsProxy != null) {
+ streamsProxy.dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ streamsProxy = null;
+ }
+ // Mark the collector initialization as done
+ collector.initDone();
+
+ // Dissociate the channel
+ channel = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#terminate()
+ */
+ @Override
+ public void terminate() {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (processContext != null && processContext.canTerminate()) {
+ // Try to terminate the process the usual way first (sending SIGTERM)
+ processContext.terminate(new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ onTerminateDone(processContext, error);
+ }
+ });
+ }
+
+ }
+ };
+
+ if (Protocol.isDispatchThread()) {
+ runnable.run();
+ }
+ else {
+ Protocol.invokeAndWait(runnable);
+ }
+ }
+
+ /**
+ * Check if the process context really died after sending SIGTERM.
+ * <p>
+ * Called from {@link #terminate()}.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @param error The exception in case {@link #terminate()} returned with an error or <code>null</code>.
+ */
+ protected void onTerminateDone(IProcesses.ProcessContext context, Exception error) {
+ Assert.isNotNull(context);
+
+ // If the terminate of the remote process context failed, give a warning to the user
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processTerminateFailed, context.getName());
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ // No error from terminate, this does not mean that the process went down
+ // really -> SIGTERM might have been ignored from the process!
+ else {
+ final IProcesses.ProcessContext finContext = context;
+ // Let's see if we can still get information about the context
+ getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
+ @Override
+ public void doneGetContext(IToken token, Exception error, ProcessContext context) {
+ // In case there is no error and we do get back an process context,
+ // the process must be still running, having ignored the SIGTERM.
+ if (error == null && context != null && context.getID().equals(finContext.getID())) {
+ // Let's send a SIGHUP next.
+ getSvcProcesses().signal(context.getID(), 15, new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ onSignalSIGHUPDone(finContext, error);
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Check if the process context died after sending SIGHUP.
+ * <p>
+ * Called from {@link #onTerminateDone(IProcesses.ProcessContext, Exception)}.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @param error The exception in case sending the signal returned with an error or <code>null</code>.
+ */
+ protected void onSignalSIGHUPDone(IProcesses.ProcessContext context, Exception error) {
+ Assert.isNotNull(context);
+
+ // If the terminate of the remote process context failed, give a warning to the user
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGHUP(15)", context.getName()); //$NON-NLS-1$
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ // No error from terminate, this does not mean that the process went down
+ // really -> SIGTERM might have been ignored from the process!
+ else {
+ final IProcesses.ProcessContext finContext = context;
+ // Let's see if we can still get information about the context
+ getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
+ @Override
+ public void doneGetContext(IToken token, Exception error, ProcessContext context) {
+ // In case there is no error and we do get back an process context,
+ // the process must be still running, having ignored the SIGHUP.
+ if (error == null && context != null && context.getID().equals(finContext.getID())) {
+ // Finally send a SIGKILL.
+ getSvcProcesses().signal(context.getID(), 9, new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGKILL(15)", finContext.getName()); //$NON-NLS-1$
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
+ */
+ @Override
+ public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) {
+ Assert.isNotNull(peer);
+ Assert.isNotNull(properties);
+
+ // Normalize the callback
+ if (callback == null) {
+ this.callback = new Callback() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
+ */
+ @Override
+ public void internalDone(Object caller, IStatus status) {
+ }
+ };
+ }
+ else {
+ this.callback = callback;
+ }
+
+ // Remember the process properties
+ this.properties = properties;
+
+ // Open a dedicated channel to the given peer
+ Map<String, Boolean> flags = new HashMap<String, Boolean>();
+ flags.put(IChannelManager.FLAG_FORCE_NEW, Boolean.TRUE);
+ if (channel != null && channel.getState() == IChannel.STATE_OPEN) {
+ onChannelOpenDone(peer);
+ }
+ else {
+ Tcf.getChannelManager().openChannel(peer, flags, new IChannelManager.DoneOpenChannel() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel)
+ */
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if (error == null) {
+ ProcessLauncher.this.channel = channel;
+ onChannelOpenDone(peer);
+ } else {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
+ error);
+ invokeCallback(status, null);
+ }
+ }
+ });
+ }
+ }
+
+ protected void onChannelOpenDone(final IPeer peer) {
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ // Attach a channel listener so we can dispose ourself if the channel
+ // is closed from the remote side.
+ channel.addChannelListener(new IChannelListener() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened()
+ */
+ @Override
+ public void onChannelOpened() {
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable)
+ */
+ @Override
+ public void onChannelClosed(Throwable error) {
+ if (error != null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
+ error);
+ invokeCallback(status, null);
+ }
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int)
+ */
+ @Override
+ public void congestionLevel(int level) {
+ }
+ });
+
+
+ // Check if the channel is in connected state
+ if (channel.getState() != IChannel.STATE_OPEN) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ Messages.ProcessLauncher_error_channelNotConnected,
+ new IllegalStateException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Do some very basic sanity checking on the process properties
+ if (properties.getStringProperty(PROP_PROCESS_PATH) == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ Messages.ProcessLauncher_error_missingProcessPath,
+ new IllegalArgumentException(PROP_PROCESS_PATH));
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Get the process and streams services
+ svcProcesses = channel.getRemoteService(IProcesses.class);
+ if (svcProcesses == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IProcesses.class.getName()),
+ null);
+
+ invokeCallback(status, null);
+ return;
+ }
+
+ svcStreams = channel.getRemoteService(IStreams.class);
+ if (svcStreams == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IStreams.class.getName()),
+ null);
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Execute the launch now
+ executeLaunch();
+ }
+ });
+ }
+
+ /**
+ * Executes the launch of the remote process.
+ */
+ protected void executeLaunch() {
+ // Get the process properties container
+ final IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // If a console should be associated, a streams listener needs to be created
+ if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)
+ || properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
+ // Create the streams listener
+ streamsListener = createStreamsListener();
+ // If available, we need to subscribe to the streams.
+ if (streamsListener != null) {
+ getSvcStreams().subscribe(IProcesses.NAME, streamsListener, new IStreams.DoneSubscribe() {
+ @Override
+ public void doneSubscribe(IToken token, Exception error) {
+ // In case the subscribe to the stream fails, we pass on
+ // the error to the user and stop the launch
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, Messages.ProcessLauncher_cause_subscribeFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Initialize the console or output file
+ onSubscribeStreamsDone();
+ }
+ }
+ });
+ } else {
+ // No streams to attach to -> go directly to the process launch
+ onAttachStreamsDone();
+ }
+ } else {
+ // No streams to attach to -> go directly to the process launch
+ onAttachStreamsDone();
+ }
+ }
+
+ /**
+ * Initialize and attach the output console and/or the output file.
+ * <p>
+ * Called from {@link IStreams#subscribe(String, org.eclipse.tcf.services.IStreams.StreamsListener, org.eclipse.tcf.services.IStreams.DoneSubscribe)}.
+ */
+ protected void onSubscribeStreamsDone() {
+ // Get the process properties container
+ IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // The streams got subscribed, check if we shall attach the console
+ if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)) {
+ // If no specific streams proxy is set, the output redirection will default
+ // to the standard terminals console view
+ if (streamsProxy == null) {
+ // Register the notification listener to listen to the console disposal
+ eventListener = new ProcessLauncherEventListener(this);
+ EventManager.getInstance().addEventListener(eventListener, DisposedEvent.class);
+
+ // Get the terminal service
+ ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
+ // If not available, we cannot fulfill this request
+ if (terminal != null) {
+ // Create the terminal streams settings
+ PropertiesContainer props = new PropertiesContainer();
+ props.setProperty(ITerminalsConnectorConstants.PROP_CONNECTOR_TYPE_ID, "org.eclipse.tcf.te.ui.terminals.type.streams"); //$NON-NLS-1$
+ props.setProperty(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tcf.te.ui.terminals.TerminalsView"); //$NON-NLS-1$
+ // Set the terminal tab title
+ String terminalTitle = getTerminalTitle();
+ if (terminalTitle != null) {
+ props.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
+ }
+
+ // Create and store the streams which will be connected to the terminals stdin
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
+ // Create and store the streams the terminal will see as stdout
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
+ // Create and store the streams the terminal will see as stderr
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
+
+ // Copy the terminal properties
+ props.setProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO));
+ props.setProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR, properties.getStringProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR));
+
+ // The custom data object is the process launcher itself
+ props.setProperty(ITerminalsConnectorConstants.PROP_DATA, this);
+
+ // Open the console
+ terminal.openConsole(props, null);
+ }
+ } else {
+ // Create and connect the streams which will be connected to the terminals stdin
+ streamsProxy.connectInputStreamMonitor(connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
+ // Create and store the streams the terminal will see as stdout
+ streamsProxy.connectOutputStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
+ // Create and store the streams the terminal will see as stderr
+ streamsProxy.connectErrorStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
+ }
+ }
+
+ // The streams got subscribed, check if we shall configure the output redirection to a file
+ if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
+ // Get the file name where to redirect the process output to
+ String filename = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE);
+ try {
+ // Create the receiver instance. If the file already exist, we
+ // overwrite the file content.
+ StreamsDataReceiver receiver = new StreamsDataReceiver(new BufferedWriter(new FileWriter(filename)),
+ new String[] { IProcesses.PROP_STDOUT_ID, IProcesses.PROP_STDERR_ID });
+ // Register the receiver to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
+ }
+ } catch (IOException e) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ e.getLocalizedMessage() != null ? e.getLocalizedMessage() : Messages.ProcessLauncher_cause_ioexception);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e);
+ invokeCallback(status, null);
+ }
+ }
+
+ // Launch the process
+ onAttachStreamsDone();
+ }
+
+ /**
+ * Returns the terminal title string.
+ * <p>
+ * The default implementation constructs a title like &quot;<process> (Start time) [channel name]&quot;.
+ *
+ * @return The terminal title string or <code>null</code>.
+ */
+ protected String getTerminalTitle() {
+ if (properties == null) {
+ return null;
+ }
+
+ StringBuilder title = new StringBuilder();
+
+ IPath processPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH));
+ IPath monitoredProcessPath = null;
+ if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH) != null) {
+ monitoredProcessPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH));
+ }
+
+ // In case, we do have a monitored process path here, we construct the title
+ // as <monitor_app_basename>: <monitored_app>"
+ if (monitoredProcessPath != null) {
+ title.append(processPath.lastSegment());
+ title.append(": "); //$NON-NLS-1$
+ processPath = monitoredProcessPath;
+ }
+
+ // Avoid very long terminal title's by shortening the path if it has more than 3 segments
+ if (processPath.segmentCount() > 3) {
+ title.append(".../"); //$NON-NLS-1$
+ title.append(processPath.lastSegment());
+ } else {
+ title.append(processPath.toString());
+ }
+
+ // Get the peer name
+ final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(IProcessLauncher.PROP_CONNECTION_NAME));
+ if (peerName.get() == null) {
+ // Query the peer from the open channel
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (channel != null) {
+ peerName.set(channel.getRemotePeer().getName());
+ }
+ }
+ };
+
+ if (Protocol.isDispatchThread()) {
+ runnable.run();
+ }
+ else {
+ Protocol.invokeAndWait(runnable);
+ }
+ }
+
+ if (peerName.get() != null) {
+ title.append(" [").append(peerName.get()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ String date = format.format(new Date(System.currentTimeMillis()));
+ title.append(" (").append(date).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return title.toString();
+ }
+
+ /**
+ * Connects the given stream id's to a local {@link InputStream} instance.
+ *
+ * @param streamsListener The streams listener. Must not be <code>null</code>.
+ * @param streamIds The stream id's. Must not be <code>null</code>.
+ *
+ * @return The local input stream instance or <code>null</code>.
+ */
+ protected InputStream connectRemoteInputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
+ Assert.isNotNull(streamsListener);
+ Assert.isNotNull(streamIds);
+
+ InputStream stream = null;
+
+ // Create the output stream receiving the data from remote
+ PipedOutputStream remoteStreamDataReceiverStream = new PipedOutputStream();
+ // Create the piped input stream instance
+ try { stream = new PipedInputStream(remoteStreamDataReceiverStream); } catch (IOException e) { /* ignored on purpose */ }
+
+ // If the input stream creation succeeded, connect the data receiver
+ if (stream != null) {
+ StreamsDataReceiver receiver = new StreamsDataReceiver(new OutputStreamWriter(remoteStreamDataReceiverStream), streamIds);
+ // Register the data receiver to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * Connects the given stream id's to a local {@link OutputStream} instance.
+ *
+ * @param streamsListener The streams listener. Must not be <code>null</code>.
+ * @param streamIds The stream id's. Must not be <code>null</code>.
+ *
+ * @return The local output stream instance or <code>null</code>.
+ */
+ protected OutputStream connectRemoteOutputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
+ Assert.isNotNull(streamsListener);
+ Assert.isNotNull(streamIds);
+
+ PipedInputStream inStream = null;
+
+ // Create the output stream receiving the data from local
+ PipedOutputStream stream = new PipedOutputStream();
+ // Create the piped input stream instance
+ try { inStream = new PipedInputStream(stream); } catch (IOException e) { stream = null; }
+
+ // If the stream creation succeeded, connect the data provider
+ if (stream != null && inStream != null) {
+ StreamsDataProvider provider = new StreamsDataProvider(new InputStreamReader(inStream), streamIds);
+ // Register the data provider to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).setDataProvider(provider);
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * Queries the initial new process environment from remote.
+ */
+ protected void onAttachStreamsDone() {
+ // Query the default environment for a new process
+ getSvcProcesses().getEnvironment(new IProcesses.DoneGetEnvironment() {
+ @Override
+ public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) {
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = Messages.ProcessLauncher_error_getEnvironmentFailed;
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Initiate the process launch
+ onGetEnvironmentDone(environment);
+ }
+ }
+ });
+ }
+
+ /**
+ * Initiate the process launch.
+ * <p>
+ * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}.
+ */
+ protected void onGetEnvironmentDone(final Map<String, String> environment) {
+ // Get the process properties container
+ final IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Create the process listener
+ processesListener = createProcessesListener();
+ if (processesListener != null) {
+ getSvcProcesses().addListener(processesListener);
+ }
+
+ // Get the process attributes
+ String processPath = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH);
+
+ String[] processArgs = (String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS);
+ // Assure that the first argument is the process path itself
+ if (!(processArgs != null && processArgs.length > 0 && processPath.equals(processArgs[0]))) {
+ // Prepend the process path to the list of arguments
+ List<String> args = processArgs != null ? new ArrayList<String>(Arrays.asList(processArgs)) : new ArrayList<String>();
+ args.add(0, processPath);
+ processArgs = args.toArray(new String[args.size()]);
+ }
+
+ String processCWD = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_CWD);
+ // If the process working directory is not explicitly set, default to the process path directory
+ if (processCWD == null || "".equals(processCWD.trim())) { //$NON-NLS-1$
+ processCWD = new Path(processPath).removeLastSegments(1).toString();
+ }
+
+ // Merge the initial process environment and the desired process environment
+ Map<String, String> processEnv = new HashMap<String, String>(environment);
+ Map<String, String> processEnvDiff = (Map<String, String>)properties.getProperty(IProcessLauncher.PROP_PROCESS_ENV);
+ if (processEnvDiff != null && !processEnvDiff.isEmpty()) {
+ processEnv.putAll(processEnvDiff);
+ }
+ // Assure that the TERM variable is set to "ansi"
+ processEnv.put("TERM", "ansi"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ boolean attach = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ATTACH);
+
+ // Launch the remote process
+ getSvcProcesses().start(processCWD, processPath, processArgs, processEnv, attach, new IProcesses.DoneStart() {
+ @Override
+ public void doneStart(IToken token, Exception error, ProcessContext process) {
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Register the process context to the listener
+ onProcessLaunchDone(process);
+ }
+ }
+ });
+ }
+
+ /**
+ * Register the process context to the listeners.
+ *
+ * @param process The process context or <code>null</code>.
+ */
+ protected void onProcessLaunchDone(ProcessContext process) {
+ // Register the process context with the listeners
+ if (process != null) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_PROCESS_LAUNCHER)) {
+ CoreBundleActivator.getTraceHandler().trace("Process context created: id='" + process.getID() + "', name='" + process.getName() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_PROCESS_LAUNCHER, IStatus.INFO, getClass());
+ }
+
+ // Remember the process context
+ processContext = process;
+
+ // Push the process context to the listeners
+ if (getStreamsListener() instanceof IProcessContextAwareListener) {
+ ((IProcessContextAwareListener)getStreamsListener()).setProcessContext(process);
+ }
+ if (getProcessesListener() instanceof IProcessContextAwareListener) {
+ ((IProcessContextAwareListener)getProcessesListener()).setProcessContext(process);
+ }
+
+ // Send a notification
+ ProcessStateChangeEvent event = createRemoteProcessStateChangeEvent(process);
+ if (event != null) {
+ EventManager.getInstance().fireEvent(event);
+ }
+ }
+
+ // Invoke the callback to signal that we are done
+ invokeCallback(Status.OK_STATUS, process);
+ }
+
+ /**
+ * Creates a new remote process state change event instance.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @return The event instance or <code>null</code>.
+ */
+ protected ProcessStateChangeEvent createRemoteProcessStateChangeEvent(IProcesses.ProcessContext context) {
+ Assert.isNotNull(context);
+ return new ProcessStateChangeEvent(context, ProcessStateChangeEvent.EVENT_PROCESS_CREATED, Boolean.FALSE, Boolean.TRUE, -1);
+ }
+
+ /**
+ * Invoke the callback with the given parameters. If the given status severity
+ * is {@link IStatus#ERROR}, the process launcher object is disposed automatically.
+ *
+ * @param status The status. Must not be <code>null</code>.
+ * @param result The result object or <code>null</code>.
+ */
+ protected void invokeCallback(IStatus status, Object result) {
+ // Dispose the process launcher if we report an error
+ if (status.getSeverity() == IStatus.ERROR) {
+ dispose();
+ }
+
+ // Invoke the callback
+ ICallback callback = getCallback();
+ if (callback != null) {
+ callback.setResult(result);
+ callback.done(this, status);
+ }
+ }
+
+ /**
+ * Returns the channel instance.
+ *
+ * @return The channel instance or <code>null</code> if none.
+ */
+ public final IChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * Returns the process properties container.
+ *
+ * @return The process properties container or <code>null</code> if none.
+ */
+ public final IPropertiesContainer getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns the processes service instance.
+ *
+ * @return The processes service instance or <code>null</code> if none.
+ */
+ public final IProcesses getSvcProcesses() {
+ return svcProcesses;
+ }
+
+ /**
+ * Returns the streams service instance.
+ *
+ * @return The streams service instance or <code>null</code> if none.
+ */
+ public final IStreams getSvcStreams() {
+ return svcStreams;
+ }
+
+ /**
+ * Returns the callback instance.
+ *
+ * @return The callback instance or <code>null</code> if none.
+ */
+ protected final ICallback getCallback() {
+ return callback;
+ }
+
+ /**
+ * Create the streams listener instance.
+ *
+ * @return The streams listener instance or <code>null</code> if none.
+ */
+ protected IStreams.StreamsListener createStreamsListener() {
+ return new ProcessStreamsListener(this);
+ }
+
+ /**
+ * Returns the streams listener instance.
+ *
+ * @return The streams listener instance or <code>null</code>.
+ */
+ protected final IStreams.StreamsListener getStreamsListener() {
+ return streamsListener;
+ }
+
+ /**
+ * Create the processes listener instance.
+ *
+ * @return The processes listener instance or <code>null</code> if none.
+ */
+ protected IProcesses.ProcessesListener createProcessesListener() {
+ return new ProcessProcessesListener(this);
+ }
+
+ /**
+ * Returns the processes listener instance.
+ *
+ * @return The processes listener instance or <code>null</code> if none.
+ */
+ protected final IProcesses.ProcessesListener getProcessesListener() {
+ return processesListener;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (adapter.isAssignableFrom(IProcesses.ProcessesListener.class)) {
+ return processesListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) {
+ return streamsListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.class)) {
+ return svcStreams;
+ }
+ else if (adapter.isAssignableFrom(IProcesses.class)) {
+ return svcProcesses;
+ }
+ else if (adapter.isAssignableFrom(IChannel.class)) {
+ return channel;
+ }
+ else if (adapter.isAssignableFrom(IPropertiesContainer.class)) {
+ return properties;
+ }
+ else if (adapter.isAssignableFrom(IProcesses.ProcessContext.class)) {
+ return processContext;
+ }
+ else if (adapter.isAssignableFrom(this.getClass())) {
+ return this;
+ }
+
+
+ return super.getAdapter(adapter);
+ }
+
+ /**
+ * Makes a space separated string from the given array.
+ *
+ * @param array The string array or <code>null</code>.
+ * @return The string.
+ */
+ String makeString(String[] array) {
+ if (array == null) {
+ return ""; //$NON-NLS-1$
+ }
+ StringBuilder result = new StringBuilder();
+ for (String element : array) {
+ if (result.length() > 0) {
+ result.append(' ');
+ }
+ result.append(element);
+ }
+ return result.toString();
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java
index 9da0fc1a1..f0a563064 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java
@@ -405,6 +405,7 @@ public class TerminalsLauncher extends PlatformObject implements ITerminalsLaunc
PropertiesContainer props = new PropertiesContainer();
props.setProperty(ITerminalsConnectorConstants.PROP_CONNECTOR_TYPE_ID, "org.eclipse.tcf.te.ui.terminals.type.terminals"); //$NON-NLS-1$
props.setProperty(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tcf.te.ui.terminals.TerminalsView"); //$NON-NLS-1$
+ props.setProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW, true);
// Set the terminal tab title
String terminalTitle = getTerminalTitle();
if (terminalTitle != null) {
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java
index ec094c2ae..21ea4aa62 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java
@@ -311,18 +311,7 @@ public class TerminalsStreamsListener implements IStreams.StreamsListener, ITerm
// Disconnect from the stream
if (svcStreams != null) {
- svcStreams.disconnect(streamId, new IStreams.DoneDisconnect() {
- @Override
- @SuppressWarnings("synthetic-access")
- public void doneDisconnect(IToken token, Exception error) {
- synchronized (this) {
- // Mark the runnable definitely stopped
- stopped = true;
- }
- // Disconnect is done, ignore any error, invoke the callback
- if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
- }
- });
+ disconnect(svcStreams, streamId);
} else {
synchronized (this) {
// Mark the runnable definitely stopped
@@ -374,6 +363,36 @@ public class TerminalsStreamsListener implements IStreams.StreamsListener, ITerm
}
/**
+ * Disconnects from the stream.
+ *
+ * @param service The streams service. Must not be <code>null</code>.
+ * @param streamId The stream id. Must not be <code>null</code>.
+ */
+ protected final void disconnect(final IStreams service, final String streamId) {
+ Assert.isNotNull(service);
+ Assert.isNotNull(streamId);
+ Assert.isTrue(!Protocol.isDispatchThread());
+
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ service.disconnect(streamId, new IStreams.DoneDisconnect() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void doneDisconnect(IToken token, Exception error) {
+ synchronized (this) {
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ // Disconnect is done, ignore any error, invoke the callback
+ if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
+ }
+ });
+ }
+ });
+ }
+
+ /**
* Notify the data receiver that some data has been received.
*
* @param data The data or <code>null</code>.
@@ -560,25 +579,7 @@ public class TerminalsStreamsListener implements IStreams.StreamsListener, ITerm
// Disconnect from the stream
if (svcStreams != null) {
- // Write EOS first
- svcStreams.eos(streamId, new IStreams.DoneEOS() {
- @Override
- public void doneEOS(IToken token, Exception error) {
- // Disconnect now
- svcStreams.disconnect(streamId, new IStreams.DoneDisconnect() {
- @Override
- @SuppressWarnings("synthetic-access")
- public void doneDisconnect(IToken token, Exception error) {
- synchronized (this) {
- // Mark the runnable definitely stopped
- stopped = true;
- }
- // Disconnect is done, ignore any error, invoke the callback
- if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
- }
- });
- }
- });
+ disconnect(svcStreams, streamId);
} else {
synchronized (this) {
// Mark the runnable definitely stopped
@@ -628,7 +629,44 @@ public class TerminalsStreamsListener implements IStreams.StreamsListener, ITerm
// Execute the write
task.get();
}
- }
+
+ /**
+ * Disconnects from the stream.
+ *
+ * @param service The streams service. Must not be <code>null</code>.
+ * @param streamId The stream id. Must not be <code>null</code>.
+ */
+ protected final void disconnect(final IStreams service, final String streamId) {
+ Assert.isNotNull(service);
+ Assert.isNotNull(streamId);
+ Assert.isTrue(!Protocol.isDispatchThread());
+
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ // Write EOS first
+ service.eos(streamId, new IStreams.DoneEOS() {
+ @Override
+ public void doneEOS(IToken token, Exception error) {
+ // Disconnect now
+ service.disconnect(streamId, new IStreams.DoneDisconnect() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void doneDisconnect(IToken token, Exception error) {
+ synchronized (this) {
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ // Disconnect is done, ignore any error, invoke the callback
+ if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+}
/**
* Constructor.
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.serial/src/org/eclipse/tcf/te/ui/terminals/serial/launcher/SerialLauncherDelegate.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.serial/src/org/eclipse/tcf/te/ui/terminals/serial/launcher/SerialLauncherDelegate.java
index eb03def86..b68ce071b 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.serial/src/org/eclipse/tcf/te/ui/terminals/serial/launcher/SerialLauncherDelegate.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.serial/src/org/eclipse/tcf/te/ui/terminals/serial/launcher/SerialLauncherDelegate.java
@@ -12,6 +12,7 @@ package org.eclipse.tcf.te.ui.terminals.serial.launcher;
import java.text.DateFormat;
import java.util.Date;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
@@ -50,6 +51,8 @@ public class SerialLauncherDelegate extends AbstractLauncherDelegate {
*/
@Override
public void execute(IPropertiesContainer properties, ICallback callback) {
+ Assert.isNotNull(properties);
+
// Set the terminal tab title
String terminalTitle = getTerminalTitle(properties);
if (terminalTitle != null) {
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.ssh/src/org/eclipse/tcf/te/ui/terminals/ssh/launcher/SshLauncherDelegate.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.ssh/src/org/eclipse/tcf/te/ui/terminals/ssh/launcher/SshLauncherDelegate.java
index 5e898e455..9ae047c8d 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.ssh/src/org/eclipse/tcf/te/ui/terminals/ssh/launcher/SshLauncherDelegate.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.ssh/src/org/eclipse/tcf/te/ui/terminals/ssh/launcher/SshLauncherDelegate.java
@@ -13,6 +13,7 @@ package org.eclipse.tcf.te.ui.terminals.ssh.launcher;
import java.text.DateFormat;
import java.util.Date;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
@@ -51,12 +52,20 @@ public class SshLauncherDelegate extends AbstractLauncherDelegate {
*/
@Override
public void execute(IPropertiesContainer properties, ICallback callback) {
+ Assert.isNotNull(properties);
+
// Set the terminal tab title
String terminalTitle = getTerminalTitle(properties);
if (terminalTitle != null) {
properties.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
}
+ // For SSH terminals, force a new terminal tab each time it is launched,
+ // if not set otherwise from outside
+ if (properties.getProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW) == null) {
+ properties.setProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW, true);
+ }
+
// Get the terminal service
ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
// If not available, we cannot fulfill this request
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.telnet/src/org/eclipse/tcf/te/ui/terminals/telnet/launcher/TelnetLauncherDelegate.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.telnet/src/org/eclipse/tcf/te/ui/terminals/telnet/launcher/TelnetLauncherDelegate.java
index cff38f7a1..392bf6a67 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.telnet/src/org/eclipse/tcf/te/ui/terminals/telnet/launcher/TelnetLauncherDelegate.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals.telnet/src/org/eclipse/tcf/te/ui/terminals/telnet/launcher/TelnetLauncherDelegate.java
@@ -13,6 +13,7 @@ package org.eclipse.tcf.te.ui.terminals.telnet.launcher;
import java.text.DateFormat;
import java.util.Date;
+import org.eclipse.core.runtime.Assert;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
@@ -26,7 +27,7 @@ import org.eclipse.tcf.te.ui.terminals.telnet.controls.TelnetWizardConfiguration
import org.eclipse.tcf.te.ui.terminals.telnet.nls.Messages;
/**
- * SSH launcher delegate implementation.
+ * Telnet launcher delegate implementation.
*/
public class TelnetLauncherDelegate extends AbstractLauncherDelegate {
@@ -51,12 +52,20 @@ public class TelnetLauncherDelegate extends AbstractLauncherDelegate {
*/
@Override
public void execute(IPropertiesContainer properties, ICallback callback) {
+ Assert.isNotNull(properties);
+
// Set the terminal tab title
String terminalTitle = getTerminalTitle(properties);
if (terminalTitle != null) {
properties.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
}
+ // For Telnet terminals, force a new terminal tab each time it is launched,
+ // if not set otherwise from outside
+ if (properties.getProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW) == null) {
+ properties.setProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW, true);
+ }
+
// Get the terminal service
ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
// If not available, we cannot fulfill this request
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/interfaces/ImageConsts.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/interfaces/ImageConsts.java
index 59fc23be6..cafce54c4 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/interfaces/ImageConsts.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/interfaces/ImageConsts.java
@@ -45,7 +45,7 @@ public interface ImageConsts {
public final static String IMAGE_DIR_EVIEW = "eview16/"; //$NON-NLS-1$
/**
- * The key to access the terminal consoles view image.
+ * The key to access the terminals console view image.
*/
public static final String VIEW_Terminals = "TerminalsView"; //$NON-NLS-1$
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/manager/ConsoleManager.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/manager/ConsoleManager.java
index ad6b045f2..8436d2baf 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/manager/ConsoleManager.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/manager/ConsoleManager.java
@@ -66,7 +66,7 @@ public class ConsoleManager {
List<IViewReference> references = new ArrayList<IViewReference>(Arrays.asList(page.getViewReferences()));
for (IViewReference reference : oldReferences) {
if (references.contains(reference)) continue;
- // Previous visible terminal console view reference, make visible again
+ // Previous visible terminals console view reference, make visible again
try {
page.showView(reference.getId(), reference.getSecondaryId(), IWorkbenchPage.VIEW_VISIBLE);
} catch (PartInitException e) { /* Failure on part instantiation is ignored */ }
@@ -134,7 +134,9 @@ public class ConsoleManager {
* <p>
* <b>Note:</b> The method must be called within the UI thread.
*
- * @param id The terminals console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
+ * @param secondaryId The terminals console view secondary id or <code>null</code>.
+ *
* @return The console view instance if available or <code>null</code> otherwise.
*/
public ITerminalsView findConsoleView(String id, String secondaryId) {
@@ -146,7 +148,7 @@ public class ConsoleManager {
IWorkbenchPage page = getActiveWorkbenchPage();
if (page != null) {
// Look for the view
- IViewPart part=getTerminalsViewWithSecondaryId(id != null ? id : IUIConstants.ID, secondaryId);
+ IViewPart part = getTerminalsViewWithSecondaryId(id != null ? id : IUIConstants.ID, secondaryId);
// Check the interface
if (part instanceof ITerminalsView) {
view = (ITerminalsView)part;
@@ -289,11 +291,11 @@ public class ConsoleManager {
}
/**
- * Show the terminal console view specified by the given id.
+ * Show the terminals console view specified by the given id.
* <p>
* <b>Note:</b> The method must be called within the UI thread.
*
- * @param id The terminal console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
*/
public IViewPart showConsoleView(String id, String secondaryId) {
Assert.isNotNull(Display.findDisplay(Thread.currentThread()));
@@ -317,9 +319,9 @@ public class ConsoleManager {
}
/**
- * Bring the terminal console view, specified by the given id, to the top of the view stack.
+ * Bring the terminals console view, specified by the given id, to the top of the view stack.
*
- * @param id The terminal console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
* @param activate If <code>true</code> activate the console view.
*/
private IViewPart bringToTop(String id, boolean activate) {
@@ -384,13 +386,14 @@ public class ConsoleManager {
* <p>
* <b>Note:</b> The method must be called within the UI thread.
*
- * @param id The terminal console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
* @param title The console title. Must not be <code>null</code>.
* @param connector The terminal connector. Must not be <code>null</code>.
* @param data The custom terminal data node or <code>null</code>.
* @param activate If <code>true</code> activate the console view.
+ * @param forceNew If <code>true</code> a new console tab is created even if another one matches the criteria.
*/
- public void openConsole(String id, String title, ITerminalConnector connector, Object data, boolean activate) {
+ public void openConsole(String id, String title, ITerminalConnector connector, Object data, boolean activate, boolean forceNew) {
Assert.isNotNull(title);
Assert.isNotNull(connector);
Assert.isNotNull(Display.findDisplay(Thread.currentThread()));
@@ -411,8 +414,8 @@ public class ConsoleManager {
CTabItem item = findConsole(id, secId, title, connector, data);
- // If no existing console exist -> Create the tab item
- if (item == null) {
+ // If no existing console exist or forced -> Create the tab item
+ if (item == null || forceNew) {
// If configured, check all existing tab items if they are associated
// with terminated consoles
if (UIPlugin.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_REMOVE_TERMINATED_TERMINALS)) {
@@ -443,7 +446,8 @@ public class ConsoleManager {
* <b>Note:</b> The method must be called within the UI thread.
* <b>Note:</b> The method will handle unified console titles itself.
*
- * @param id The terminal console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
+ * @param secondaryId The terminals console view secondary id or <code>null</code>.
* @param title The console title. Must not be <code>null</code>.
* @param connector The terminal connector. Must not be <code>null</code>.
* @param data The custom terminal data node or <code>null</code>.
@@ -471,7 +475,7 @@ public class ConsoleManager {
* <p>
* <b>Note:</b> The method will handle unified console titles itself.
*
- * @param id The terminal console view id or <code>null</code> to show the default terminal console view.
+ * @param id The terminals console view id or <code>null</code> to show the default terminals console view.
* @param title The console title. Must not be <code>null</code>.
* @param connector The terminal connector. Must not be <code>null</code>.
* @param data The custom terminal data node or <code>null</code>.
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/services/TerminalService.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/services/TerminalService.java
index e7c8655d9..f9b42d84d 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/services/TerminalService.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/services/TerminalService.java
@@ -75,7 +75,7 @@ public class TerminalService extends AbstractService implements ITerminalService
String title = properties.getStringProperty(ITerminalsConnectorConstants.PROP_TITLE);
Object data = properties.getProperty(ITerminalsConnectorConstants.PROP_DATA);
- // Normalize the terminal console view id
+ // Normalize the terminals console view id
id = normalizeId(id, data);
// Normalize the terminal console tab title
title = normalizeTitle(title, data);
@@ -115,7 +115,7 @@ public class TerminalService extends AbstractService implements ITerminalService
* @param id The terminals view id or <code>null</code>.
* @param data The custom data object or <code>null</code>.
*
- * @return The normalized terminal console view id.
+ * @return The normalized terminals console view id.
*/
protected String normalizeId(String id, Object data) {
return id != null ? id : IUIConstants.ID;
@@ -173,8 +173,10 @@ public class TerminalService extends AbstractService implements ITerminalService
executeServiceOperation(properties, new TerminalServiceRunnable() {
@Override
public void run(String id, String title, ITerminalConnector connector, Object data, ICallback callback) {
+ // Determine if a new terminal tab shall be enforced
+ boolean forceNew = properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW);
// Open the new console
- ConsoleManager.getInstance().openConsole(id, title, connector, data, true);
+ ConsoleManager.getInstance().openConsole(id, title, connector, data, true, forceNew);
// Invoke the callback
if (callback != null) {
callback.done(this, Status.OK_STATUS);
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderManager.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderManager.java
index 76e7cb6d3..da94eebcc 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderManager.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderManager.java
@@ -211,7 +211,7 @@ public class TabFolderManager extends PlatformObject implements ISelectionProvid
/**
* Constructor.
*
- * @param parentView The parent terminal console view. Must not be <code>null</code>.
+ * @param parentView The parent terminals console view. Must not be <code>null</code>.
*/
public TabFolderManager(ITerminalsView parentView) {
super();
@@ -498,9 +498,9 @@ public class TabFolderManager extends PlatformObject implements ISelectionProvid
for (CTabItem item : tabFolder.getItems()) {
// Get the tab item title
titles.add(item.getText());
- // Make the proposal unique be appending (<n>) against all known titles.
- while (titles.contains(title)) title = proposal + " (" + index++ + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
+ // Make the proposal unique be appending (<n>) against all known titles.
+ while (titles.contains(title)) title = proposal + " (" + ++index + ")"; //$NON-NLS-1$ //$NON-NLS-2$
return title;
}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderMenuHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderMenuHandler.java
index bbcb0b9ae..cf2fe3ba1 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderMenuHandler.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderMenuHandler.java
@@ -36,7 +36,7 @@ import org.eclipse.ui.IWorkbenchActionConstants;
*/
@SuppressWarnings("restriction")
public class TabFolderMenuHandler extends PlatformObject {
- // Reference to the parent terminal console view
+ // Reference to the parent terminals console view
private final ITerminalsView parentView;
// Reference to the tab folder context menu manager
private MenuManager contextMenuManager;
@@ -73,7 +73,7 @@ public class TabFolderMenuHandler extends PlatformObject {
/**
* Constructor.
*
- * @param parentView The parent terminal console view. Must not be <code>null</code>.
+ * @param parentView The parent terminals console view. Must not be <code>null</code>.
*/
public TabFolderMenuHandler(ITerminalsView parentView) {
super();
@@ -82,9 +82,9 @@ public class TabFolderMenuHandler extends PlatformObject {
}
/**
- * Returns the parent terminal console view.
+ * Returns the parent terminals console view.
*
- * @return The parent terminal console view instance.
+ * @return The parent terminals console view instance.
*/
protected final ITerminalsView getParentView() {
return parentView;
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderToolbarHandler.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderToolbarHandler.java
index 9c9980780..6fba96726 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderToolbarHandler.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/tabs/TabFolderToolbarHandler.java
@@ -40,7 +40,7 @@ import org.eclipse.ui.IWorkbenchActionConstants;
*/
@SuppressWarnings("restriction")
public class TabFolderToolbarHandler extends PlatformObject {
- // Reference to the parent terminal console view
+ // Reference to the parent terminals console view
private final ITerminalsView parentView;
// Reference to the toolbar manager
private IToolBarManager toolbarManager;
@@ -77,7 +77,7 @@ public class TabFolderToolbarHandler extends PlatformObject {
/**
* Constructor.
*
- * @param parentView The parent terminal console view. Must not be <code>null</code>.
+ * @param parentView The parent terminals console view. Must not be <code>null</code>.
*/
public TabFolderToolbarHandler(ITerminalsView parentView) {
super();
@@ -86,9 +86,9 @@ public class TabFolderToolbarHandler extends PlatformObject {
}
/**
- * Returns the parent terminal console view.
+ * Returns the parent terminals console view.
*
- * @return The terminal console view instance.
+ * @return The terminals console view instance.
*/
protected final ITerminalsView getParentView() {
return parentView;
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/view/TerminalsView.java b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/view/TerminalsView.java
index 6b9eb56b9..7068e0173 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/view/TerminalsView.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.ui.terminals/src/org/eclipse/tcf/te/ui/terminals/view/TerminalsView.java
@@ -390,7 +390,7 @@ public class TerminalsView extends ViewPart implements ITerminalsView {
}
/**
- * Returns the context help id associated with the terminal console view instance.
+ * Returns the context help id associated with the terminals console view instance.
* <p>
* <b>Note:</b> The default implementation returns the view id as context help id.
*

Back to the top