Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUwe Stieber2011-11-16 08:59:51 +0000
committerUwe Stieber2011-11-16 08:59:51 +0000
commit8706fb04b6c1417ed8d5ece83bc9fca64ddfc3ce (patch)
tree35ff2855e92a05029eb1a01455c6018c573c7175 /target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src
parent67449886c09c8ec4da4f39082373ab62ccc3d978 (diff)
downloadorg.eclipse.tcf-8706fb04b6c1417ed8d5ece83bc9fca64ddfc3ce.tar.gz
org.eclipse.tcf-8706fb04b6c1417ed8d5ece83bc9fca64ddfc3ce.tar.xz
org.eclipse.tcf-8706fb04b6c1417ed8d5ece83bc9fca64ddfc3ce.zip
Target Explorer: Refactor name space from org.eclipse.tm.te.* to org.eclipse.tcf.te.*
Diffstat (limited to 'target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src')
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/activator/CoreBundleActivator.java71
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsContextAwareListener.java32
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsLauncher.java80
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/internal/tracing/ITraceIds.java31
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java825
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncherEventListener.java55
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsListener.java143
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStateChangeEvent.java75
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java907
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.java48
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.properties20
11 files changed, 2287 insertions, 0 deletions
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/activator/CoreBundleActivator.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/activator/CoreBundleActivator.java
new file mode 100644
index 000000000..3d5711ba7
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/activator/CoreBundleActivator.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.activator;
+
+import org.eclipse.tcf.te.runtime.tracing.TraceHandler;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class CoreBundleActivator implements BundleActivator {
+ // The bundle context
+ private static BundleContext context;
+ // The trace handler instance
+ private static TraceHandler traceHandler;
+
+ /**
+ * Returns the bundle context
+ *
+ * @return the bundle context
+ */
+ public static BundleContext getContext() {
+ return context;
+ }
+
+ /**
+ * Convenience method which returns the unique identifier of this plugin.
+ */
+ public static String getUniqueIdentifier() {
+ if (getContext() != null && getContext().getBundle() != null) {
+ return getContext().getBundle().getSymbolicName();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the bundles trace handler.
+ *
+ * @return The bundles trace handler.
+ */
+ public static TraceHandler getTraceHandler() {
+ if (traceHandler == null) {
+ traceHandler = new TraceHandler(getUniqueIdentifier());
+ }
+ return traceHandler;
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void start(BundleContext bundleContext) throws Exception {
+ CoreBundleActivator.context = bundleContext;
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void stop(BundleContext bundleContext) throws Exception {
+ CoreBundleActivator.context = null;
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsContextAwareListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsContextAwareListener.java
new file mode 100644
index 000000000..131104f6a
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsContextAwareListener.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.interfaces.launcher;
+
+import org.eclipse.tcf.services.ITerminals;
+
+/**
+ * Remote terminal context aware listener.
+ */
+public interface ITerminalsContextAwareListener {
+
+ /**
+ * Sets the terminals context.
+ *
+ * @param context The terminals context. Must not be <code>null</code>.
+ */
+ public void setTerminalsContext(ITerminals.TerminalContext context);
+
+ /**
+ * Returns the terminals context.
+ *
+ * @return The terminals context.
+ */
+ public ITerminals.TerminalContext getTerminalsContext();
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsLauncher.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsLauncher.java
new file mode 100644
index 000000000..7d5fc33b3
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/interfaces/launcher/ITerminalsLauncher.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.interfaces.launcher;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
+import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
+
+/**
+ * Interface to be implemented by classes providing a remote terminals launcher.
+ */
+public interface ITerminalsLauncher extends IAdaptable {
+
+ /**
+ * Property denoting the terminal PTY type.
+ * <p>
+ * <b>Note:</b> If not explicitly specified, the terminal type defaults to &quot;ansi&quot;.
+ * <p>
+ * The property type is {@link String}.
+ */
+ public static String PROP_TERMINAL_TYPE = "terminals.type"; //$NON-NLS-1$
+
+ /**
+ * Property denoting the terminal encoding.
+ * <p>
+ * The property type is {@link String}.
+ */
+ public static String PROP_TERMINAL_ENCODING = "terminals.cwd"; //$NON-NLS-1$
+
+ /**
+ * Property denoting the terminal environment.
+ * <p>
+ * The property type is {@link Map}&lt; {@link String}, {@link String} &gt;.
+ */
+ public static String PROP_TERMINAL_ENV = "terminals.env"; //$NON-NLS-1$
+
+ /**
+ * Property denoting if the terminal is redirecting it's output to an file.
+ * <p>
+ * The property type is {@link String}.
+ */
+ public static String PROP_TERMINAL_OUTPUT_REDIRECT_TO_FILE = "terminal.redirectToFile"; //$NON-NLS-1$
+
+ /**
+ * Property denoting the full name of the connection the launcher got invoked for.
+ * <p>
+ * The property type is {@link String}.
+ */
+ public static String PROP_CONNECTION_NAME = "connection.name"; //$NON-NLS-1$
+
+ /**
+ * Launch a remote terminal defined by the given launch properties at the target specified by the
+ * given peer.
+ *
+ * @param peer The peer. Must not be <code>null</code>.
+ * @param params The remote terminal properties. Must not be <code>null</code>.
+ * @param callback The callback or <code>null</code>.
+ */
+ public void launch(IPeer peer, IPropertiesContainer properties, ICallback callback);
+
+ /**
+ * Disposes the remote terminals launcher instance.
+ */
+ public void dispose();
+
+ /**
+ * Exit the launched terminal (if still running).
+ */
+ public void exit();
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/internal/tracing/ITraceIds.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/internal/tracing/ITraceIds.java
new file mode 100644
index 000000000..30e9e559b
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/internal/tracing/ITraceIds.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.internal.tracing;
+
+/**
+ * Core plug-in trace slot identifiers.
+ */
+public interface ITraceIds {
+
+ /**
+ * If activated, tracing information about the remote terminals launcher is printed out.
+ */
+ public static final String TRACE_TERMINALS_LAUNCHER = "trace/terminalsLauncher"; //$NON-NLS-1$
+
+ /**
+ * If activated, tracing information about the remote terminals listener is printed out.
+ */
+ public static final String TRACE_TERMINALS_LISTENER = "trace/terminalsListener"; //$NON-NLS-1$
+
+ /**
+ * If activated, tracing information about the remote terminals streams listener is printed out.
+ */
+ public static final String TRACE_STREAMS_LISTENER = "trace/streamsListener"; //$NON-NLS-1$
+}
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
new file mode 100644
index 000000000..bed8c1f93
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncher.java
@@ -0,0 +1,825 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.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.Date;
+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.IStatus;
+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.IStreams;
+import org.eclipse.tcf.services.ITerminals;
+import org.eclipse.tcf.services.ITerminals.TerminalContext;
+import org.eclipse.tcf.te.tcf.terminals.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener;
+import org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsLauncher;
+import org.eclipse.tcf.te.tcf.terminals.core.internal.tracing.ITraceIds;
+import org.eclipse.tcf.te.tcf.terminals.core.nls.Messages;
+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.interfaces.IChannelManager;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
+
+/**
+ * Remote terminals launcher.
+ * <p>
+ * The terminals launcher is implemented fully asynchronous.
+ */
+public class TerminalsLauncher extends PlatformObject implements ITerminalsLauncher {
+ // The channel instance
+ /* default */ IChannel channel;
+ // The terminals properties instance
+ private IPropertiesContainer properties;
+
+ // The terminals service instance
+ /* default */ ITerminals svcTerminals;
+ // The streams service instance
+ /* default */ IStreams svcStreams;
+ // The remote terminals context
+ /* default */ ITerminals.TerminalContext terminalContext;
+
+ // The callback instance
+ private ICallback callback;
+
+ // The streams listener instance
+ private IStreams.StreamsListener streamsListener = null;
+ // The terminals listener instance
+ private ITerminals.TerminalsListener terminalsListener = null;
+ // The event listener instance
+ private IEventListener eventListener = null;
+
+ /**
+ * Constructor.
+ */
+ public TerminalsLauncher() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsLauncher#dispose()
+ */
+ @Override
+ public void dispose() {
+ // Unlink the terminal context
+ terminalContext = 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 invocation delegate
+ AsyncCallbackCollector.ICallbackInvocationDelegate delegate = new AsyncCallbackCollector.ICallbackInvocationDelegate() {
+ @Override
+ public void invoke(Runnable runnable) {
+ Assert.isNotNull(runnable);
+ if (Protocol.isDispatchThread()) runnable.run();
+ else Protocol.invokeLater(runnable);
+ }
+ };
+
+ // 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) finChannel.close();
+ }
+ }, delegate);
+
+ if (streamsListener != null) {
+ // Dispose the streams listener
+ if (streamsListener instanceof TerminalsStreamsListener) {
+ ((TerminalsStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ streamsListener = null;
+ }
+
+ // Dispose the terminals listener if created
+ if (terminalsListener != null) {
+ // Dispose the terminals listener
+ if (terminalsListener instanceof TerminalsListener) {
+ ((TerminalsListener)terminalsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ terminalsListener = null;
+ // Remove the terminals listener from the terminals service
+ getSvcTerminals().removeListener(terminalsListener);
+ }
+
+ // Mark the collector initialization as done
+ collector.initDone();
+
+ // Dissociate the channel
+ channel = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsLauncher#exit()
+ */
+ @Override
+ public void exit() {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (terminalContext != null) {
+ // Exit the terminal
+ terminalContext.exit(new ITerminals.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ onExitDone(terminalContext, error);
+ }
+ });
+ }
+
+ }
+ };
+
+ if (Protocol.isDispatchThread()) runnable.run();
+ else Protocol.invokeAndWait(runnable);
+ }
+
+ /**
+ * Check if the terminal context really exited.
+ * <p>
+ * Called from {@link #exit()}.
+ *
+ * @param context The terminal context. Must not be <code>null</code>.
+ * @param error The exception in case {@link #exit()} returned with an error or <code>null</code>.
+ */
+ protected void onExitDone(ITerminals.TerminalContext context, Exception error) {
+ Assert.isNotNull(context);
+
+ // If the exit of the remote terminal context failed, give a warning to the user
+ if (error != null) {
+ String message = NLS.bind(Messages.TerminalsLauncher_error_terminalExitFailed, context.getProcessID());
+ message += NLS.bind(Messages.TerminalsLauncher_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 exit -> double-check.
+ else {
+ final ITerminals.TerminalContext finContext = context;
+ // Let's see if we can still get information about the context
+ getSvcTerminals().getContext(context.getID(), new ITerminals.DoneGetContext() {
+ @Override
+ public void doneGetContext(IToken token, Exception error, TerminalContext context) {
+ // In case there is no error and we do get back an terminal context,
+ // the terminal must be still running, having ignored the exit.
+ if (error == null && context != null && context.getID().equals(finContext.getID())) {
+ String message = NLS.bind(Messages.TerminalsLauncher_error_terminalExitFailed, context.getProcessID());
+ message += Messages.TerminalsLauncher_error_possibleCauseUnknown;
+
+ 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.terminals.core.interfaces.launcher.ITerminalsLauncher#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 terminal properties
+ this.properties = properties;
+
+ // Open a channel to the given peer
+ Tcf.getChannelManager().openChannel(peer, 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) {
+ TerminalsLauncher.this.channel = channel;
+
+ // 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.TerminalsLauncher_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.TerminalsLauncher_error_channelNotConnected,
+ new IllegalStateException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Get the terminals and streams services
+ svcTerminals = channel.getRemoteService(ITerminals.class);
+ if (svcTerminals == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.TerminalsLauncher_error_missingRequiredService, ITerminals.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.TerminalsLauncher_error_missingRequiredService, IStreams.class.getName()),
+ null);
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Execute the launch now
+ executeLaunch();
+ } else {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.TerminalsLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
+ error);
+ invokeCallback(status, null);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes the launch of the remote terminal.
+ */
+ protected void executeLaunch() {
+ // Get the 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.TerminalsLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Create the streams listener
+ streamsListener = createStreamsListener();
+ // If available, we need to subscribe to the streams.
+ if (streamsListener != null) {
+ getSvcStreams().subscribe(ITerminals.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.TerminalsLauncher_error_terminalLaunchFailed,
+ properties.getStringProperty(ITerminalsLauncher.PROP_CONNECTION_NAME));
+ message += NLS.bind(Messages.TerminalsLauncher_error_possibleCause, Messages.TerminalsLauncher_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 terminals 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 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.TerminalsLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Register the notification listener to listen to the console disposal
+ eventListener = new TerminalsLauncherEventListener(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.terminals"); //$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[] { ITerminals.PROP_STDIN_ID }));
+ // Create and store the streams the terminal will see as stdout
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { ITerminals.PROP_STDOUT_ID }));
+ // Create and store the streams the terminal will see as stderr
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { ITerminals.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 terminal launcher itself
+ props.setProperty(ITerminalsConnectorConstants.PROP_DATA, this);
+
+ // Open the console
+ terminal.openConsole(props, null);
+ }
+
+ // The streams got subscribed, check if we shall configure the output redirection to a file
+ if (properties.getStringProperty(ITerminalsLauncher.PROP_TERMINAL_OUTPUT_REDIRECT_TO_FILE) != null) {
+ // Get the file name where to redirect the terminal output to
+ String filename = properties.getStringProperty(ITerminalsLauncher.PROP_TERMINAL_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[] { ITerminals.PROP_STDOUT_ID, ITerminals.PROP_STDERR_ID });
+ // Register the receiver to the streams listener
+ if (getStreamsListener() instanceof TerminalsStreamsListener) {
+ ((TerminalsStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
+ }
+ } catch (IOException e) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.TerminalsLauncher_error_terminalLaunchFailed,
+ properties.getStringProperty(ITerminalsLauncher.PROP_CONNECTION_NAME));
+ message += NLS.bind(Messages.TerminalsLauncher_error_possibleCause,
+ e.getLocalizedMessage() != null ? e.getLocalizedMessage() : Messages.TerminalsLauncher_cause_ioexception);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e);
+ invokeCallback(status, null);
+ }
+ }
+
+ // Launch the terminal
+ onAttachStreamsDone();
+ }
+
+ /**
+ * Returns the terminal title string.
+ * <p>
+ * The default implementation constructs a title like &quot;[peer name] (Start time) &quot;.
+ *
+ * @return The terminal title string or <code>null</code>.
+ */
+ protected String getTerminalTitle() {
+ if (properties == null) {
+ return null;
+ }
+
+ StringBuilder title = new StringBuilder();
+
+ // Get the peer name
+ final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(ITerminalsLauncher.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.LONG, DateFormat.LONG);
+ 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 TerminalsStreamsListener) {
+ ((TerminalsStreamsListener)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 TerminalsStreamsListener) {
+ ((TerminalsStreamsListener)getStreamsListener()).setDataProvider(provider);
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * Initiate the terminal launch.
+ * <p>
+ * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}.
+ */
+ protected void onAttachStreamsDone() {
+ // Get the 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.TerminalsLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Create the terminal listener
+ terminalsListener = createTerminalsListener();
+ if (terminalsListener != null) {
+ getSvcTerminals().addListener(terminalsListener);
+ }
+
+ // Get the terminal attributes
+
+ // Terminal Type: Default to "ansi" if not explicitly specified
+ String type = properties.getStringProperty(ITerminalsLauncher.PROP_TERMINAL_TYPE);
+ if (type == null || "".equals(type.trim())) type = "ansi"; //$NON-NLS-1$ //$NON-NLS-2$
+
+ // Terminal Encoding: Default to "null" if not explicitly specified
+ String encoding = properties.getStringProperty(ITerminalsLauncher.PROP_TERMINAL_ENCODING);
+
+ // Environment: Default to "null" if not explicitly specified
+ Map<String, String> env = (Map<String, String>)properties.getProperty(ITerminalsLauncher.PROP_TERMINAL_ENV);
+
+ // Launch the remote terminal
+ getSvcTerminals().launch(type, encoding, makeEnvironmentArray(env), new ITerminals.DoneLaunch() {
+ @Override
+ public void doneLaunch(IToken token, Exception error, ITerminals.TerminalContext terminal) {
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.TerminalsLauncher_error_terminalLaunchFailed,
+ properties.getStringProperty(ITerminalsLauncher.PROP_CONNECTION_NAME));
+ message += NLS.bind(Messages.TerminalsLauncher_error_possibleCause,
+ error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.TerminalsLauncher_cause_startFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Register the terminal context to the listener
+ onTerminalLaunchDone(terminal);
+ }
+ }
+ });
+ }
+
+ /**
+ * Register the terminals context to the listeners.
+ *
+ * @param terminal The terminals context or <code>null</code>.
+ */
+ protected void onTerminalLaunchDone(ITerminals.TerminalContext terminal) {
+ // Register the terminal context with the listeners
+ if (terminal != null) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_TERMINALS_LAUNCHER)) {
+ CoreBundleActivator.getTraceHandler().trace("Terminal context created: id='" + terminal.getID() + "', PTY type='" + terminal.getPtyType() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_TERMINALS_LAUNCHER, IStatus.INFO, getClass());
+ }
+
+ // Remember the terminal context
+ terminalContext = terminal;
+
+ // Push the terminals context to the listeners
+ if (getStreamsListener() instanceof ITerminalsContextAwareListener) {
+ ((ITerminalsContextAwareListener)getStreamsListener()).setTerminalsContext(terminal);
+ }
+ if (getTerminalsListener() instanceof ITerminalsContextAwareListener) {
+ ((ITerminalsContextAwareListener)getTerminalsListener()).setTerminalsContext(terminal);
+ }
+
+ // Send a notification
+ TerminalsStateChangeEvent event = createRemoteTerminalsStateChangeEvent(terminal);
+ if (event != null) EventManager.getInstance().fireEvent(event);
+ }
+
+ // Invoke the callback to signal that we are done
+ invokeCallback(Status.OK_STATUS, terminal);
+ }
+
+ /**
+ * Creates a new remote terminals state change event instance.
+ *
+ * @param context The terminals context. Must not be <code>null</code>.
+ * @return The event instance or <code>null</code>.
+ */
+ protected TerminalsStateChangeEvent createRemoteTerminalsStateChangeEvent(ITerminals.TerminalContext context) {
+ Assert.isNotNull(context);
+ return new TerminalsStateChangeEvent(context, TerminalsStateChangeEvent.EVENT_TERMINAL_CREATED, Boolean.FALSE, Boolean.TRUE, -1);
+ }
+
+ /**
+ * Invoke the callback with the given parameters. If the given status severity
+ * is {@link IStatus#ERROR}, the terminals 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 terminal 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 terminals properties container.
+ *
+ * @return The terminals properties container or <code>null</code> if none.
+ */
+ public final IPropertiesContainer getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns the terminals service instance.
+ *
+ * @return The terminals service instance or <code>null</code> if none.
+ */
+ public final ITerminals getSvcTerminals() {
+ return svcTerminals;
+ }
+
+ /**
+ * 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 TerminalsStreamsListener(this);
+ }
+
+ /**
+ * Returns the streams listener instance.
+ *
+ * @return The streams listener instance or <code>null</code>.
+ */
+ protected final IStreams.StreamsListener getStreamsListener() {
+ return streamsListener;
+ }
+
+ /**
+ * Create the terminals listener instance.
+ *
+ * @return The terminals listener instance or <code>null</code> if none.
+ */
+ protected ITerminals.TerminalsListener createTerminalsListener() {
+ return new TerminalsListener(this);
+ }
+
+ /**
+ * Returns the terminals listener instance.
+ *
+ * @return The terminals listener instance or <code>null</code> if none.
+ */
+ protected final ITerminals.TerminalsListener getTerminalsListener() {
+ return terminalsListener;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (adapter.isAssignableFrom(ITerminals.TerminalsListener.class)) {
+ return terminalsListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) {
+ return streamsListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.class)) {
+ return svcStreams;
+ }
+ else if (adapter.isAssignableFrom(ITerminals.class)) {
+ return svcTerminals;
+ }
+ else if (adapter.isAssignableFrom(IChannel.class)) {
+ return channel;
+ }
+ else if (adapter.isAssignableFrom(IPropertiesContainer.class)) {
+ return properties;
+ }
+ else if (adapter.isAssignableFrom(ITerminals.TerminalContext.class)) {
+ return terminalContext;
+ }
+ else if (adapter.isAssignableFrom(this.getClass())) {
+ return this;
+ }
+
+
+ return super.getAdapter(adapter);
+ }
+
+ /**
+ * Makes an environment array out of the given map.
+ *
+ * @param env The environment map or <code>null</code>.
+ * @return The string.
+ */
+ private String[] makeEnvironmentArray(Map<String, String> env) {
+ if (env == null) return null;
+
+ List<String> envList = new ArrayList<String>();
+ for (String key : env.keySet()) {
+ String entry = key.trim() + "=" + env.get(key).trim(); //$NON-NLS-1$
+ envList.add(entry);
+ }
+
+ return envList.toArray(new String[envList.size()]);
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncherEventListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncherEventListener.java
new file mode 100644
index 000000000..b663b8a47
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsLauncherEventListener.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.launcher;
+
+import java.util.EventObject;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.tcf.te.runtime.events.DisposedEvent;
+import org.eclipse.tcf.te.runtime.interfaces.events.IEventListener;
+
+/**
+ * Remote terminals launcher default notification listener implementation.
+ * <p>
+ * <b>Note:</b> The notifications may occur in every thread!
+ */
+public class TerminalsLauncherEventListener extends PlatformObject implements IEventListener {
+ // Reference to the parent launcher
+ private final TerminalsLauncher parent;
+
+ /**
+ * Constructor.
+ *
+ * @param parent The parent launcher. Must not be <code>null</code>.
+ */
+ public TerminalsLauncherEventListener(TerminalsLauncher parent) {
+ super();
+
+ Assert.isNotNull(parent);
+ this.parent = parent;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.runtime.interfaces.events.IEventListener#eventFired(java.util.EventObject)
+ */
+ @Override
+ public void eventFired(EventObject event) {
+ if (event instanceof DisposedEvent) {
+ // Get the custom data object from the disposed event
+ Object data = ((DisposedEvent)event).getData();
+ // The custom data object must be of type TerminalsLauncher and match the parent launcher
+ if (data instanceof TerminalsLauncher && parent.equals(data)) {
+ // Terminate the remote terminal (leads to the disposal of the parent)
+ parent.exit();
+ }
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsListener.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsListener.java
new file mode 100644
index 000000000..ec70d4fda
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsListener.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.launcher;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.tcf.services.ITerminals;
+import org.eclipse.tcf.services.ITerminals.TerminalContext;
+import org.eclipse.tcf.te.tcf.terminals.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener;
+import org.eclipse.tcf.te.tcf.terminals.core.internal.tracing.ITraceIds;
+import org.eclipse.tcf.te.runtime.events.EventManager;
+import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
+
+/**
+ * Remote process processes listener implementation.
+ */
+public class TerminalsListener implements ITerminals.TerminalsListener, ITerminalsContextAwareListener {
+ // The parent terminals launcher instance
+ private final TerminalsLauncher parent;
+ // The remote terminals context
+ private ITerminals.TerminalContext context;
+ // A flag to remember if exited(...) got called
+ private boolean exitedCalled = false;
+
+ /**
+ * Constructor.
+ *
+ * @param parent The parent terminals launcher instance. Must not be <code>null</code>
+ */
+ public TerminalsListener(TerminalsLauncher parent) {
+ Assert.isNotNull(parent);
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the parent terminals launcher instance.
+ *
+ * @return The parent terminals launcher instance.
+ */
+ protected final TerminalsLauncher getParent() {
+ return parent;
+ }
+
+ /**
+ * Dispose the terminals listener instance.
+ * <p>
+ * <b>Note:</b> The terminals listener is removed from the terminals service by the parent remote terminals launcher.
+ *
+ * @param callback The callback to invoke if the dispose finished or <code>null</code>.
+ */
+ public void dispose(ICallback callback) {
+ // If exited(...) hasn't been called yet, but dispose is invoked,
+ // send a ... event to signal listeners that a terminated event won't come.
+ if (!exitedCalled && context != null) {
+ TerminalsStateChangeEvent event = new TerminalsStateChangeEvent(context, TerminalsStateChangeEvent.EVENT_LOST_COMMUNICATION, Boolean.FALSE, Boolean.TRUE, -1);
+ EventManager.getInstance().fireEvent(event);
+ }
+ // Invoke the callback
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener#setTerminalsContext(org.eclipse.tcf.services.ITerminals.TerminalContext)
+ */
+ @Override
+ public void setTerminalsContext(TerminalContext context) {
+ Assert.isNotNull(context);
+ this.context = context;
+
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_TERMINALS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("Terminals context set to: id='" + context.getID() + "', PTY type='" + context.getPtyType() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_TERMINALS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener#getTerminalsContext()
+ */
+ @Override
+ public TerminalContext getTerminalsContext() {
+ return context;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.ITerminals.TerminalsListener#exited(java.lang.String, int)
+ */
+ @Override
+ public void exited(String terminalId, int exitCode) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_TERMINALS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("Terminals context terminated: id='" + terminalId + "', exitCode='" + exitCode + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_TERMINALS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+
+ // If the exited terminal is the one we are monitoring,
+ // --> initiate the disposal of the parent remote terminal launcher
+ ITerminals.TerminalContext context = getTerminalsContext();
+ if (context != null && terminalId != null && terminalId.equals(context.getID())) {
+ // Exited got called for the associated terminal context
+ exitedCalled = true;
+ // Send a notification
+ TerminalsStateChangeEvent event = createRemoteTerminalStateChangeEvent(context, exitCode);
+ EventManager.getInstance().fireEvent(event);
+ // Dispose the parent remote process launcher
+ getParent().dispose();
+ }
+ }
+
+ /**
+ * Creates a new remote terminal state change event instance.
+ *
+ * @param context The terminal context. Must not be <code>null</code>.
+ * @return The event instance or <code>null</code>.
+ */
+ protected TerminalsStateChangeEvent createRemoteTerminalStateChangeEvent(ITerminals.TerminalContext context, int exitCode) {
+ Assert.isNotNull(context);
+ return new TerminalsStateChangeEvent(context, TerminalsStateChangeEvent.EVENT_TERMINAL_TERMINATED, Boolean.FALSE, Boolean.TRUE, exitCode);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.ITerminals.TerminalsListener#winSizeChanged(java.lang.String, int, int)
+ */
+ @Override
+ public void winSizeChanged(String terminalId, int newWidth, int newHeight) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_TERMINALS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("Terminals context window size changed: id='" + terminalId + "', newWidth='" + newWidth + "', newHeight='" + newHeight + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ 0, ITraceIds.TRACE_TERMINALS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+
+ // Pass on to the terminal widget
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStateChangeEvent.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStateChangeEvent.java
new file mode 100644
index 000000000..f95c9aadb
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStateChangeEvent.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.launcher;
+
+import org.eclipse.tcf.te.runtime.events.ChangeEvent;
+
+/**
+ * Remote terminals state change event implementation.
+ */
+public class TerminalsStateChangeEvent extends ChangeEvent {
+ private static final long serialVersionUID = -2119193125420935279L;
+
+ /**
+ * Event id signaling if a remote terminal got created.
+ */
+ public static final String EVENT_TERMINAL_CREATED = "created"; //$NON-NLS-1$
+
+ /**
+ * Event id signaling if a remote terminal terminated.
+ */
+ public static final String EVENT_TERMINAL_TERMINATED = "terminated"; //$NON-NLS-1$
+
+ /**
+ * Event id signaling that the communication with the terminal got lost, probably because of a
+ * channel closed event. Any listener waiting for a terminal termination event should react on
+ * this event and stop waiting.
+ */
+ public static final String EVENT_LOST_COMMUNICATION = "lostCommunication"; //$NON-NLS-1$
+
+ // Exit code of the remote process
+ private final int exitCode;
+
+ /**
+ * Constructor.
+ *
+ * @param source The event source. Must not be <code>null</code>.
+ * @param eventId The event id. Must not be <code>null</code>.
+ * @param oldValue The old value or <code>null</code>.
+ * @param newValue The new value or <code>null</code>.
+ */
+ public TerminalsStateChangeEvent(Object source, Object eventId, Object oldValue, Object newValue) {
+ this(source, eventId, oldValue, newValue, -1);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param source The event source. Must not be <code>null</code>.
+ * @param eventId The event id. Must not be <code>null</code>.
+ * @param oldValue The old value or <code>null</code>.
+ * @param newValue The new value or <code>null</code>.
+ * @param exitCode The process exit code or <code>-1</code> if not applicable.
+ */
+ public TerminalsStateChangeEvent(Object source, Object eventId, Object oldValue, Object newValue, int exitCode) {
+ super(source, eventId, oldValue, newValue);
+ this.exitCode = exitCode;
+ }
+
+ /**
+ * Returns the remote process exit code. The exit code is only applicable if the set event id is
+ * {@link #EVENT_TERMINAL_TERMINATED}.
+ *
+ * @return The remote process exit code or <code>-1</code> if not applicable.
+ */
+ public final int getExitCode() {
+ return exitCode;
+ }
+}
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
new file mode 100644
index 000000000..79ac2fcab
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/launcher/TerminalsStreamsListener.java
@@ -0,0 +1,907 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.launcher;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.protocol.Protocol;
+import org.eclipse.tcf.services.IStreams;
+import org.eclipse.tcf.services.ITerminals;
+import org.eclipse.tcf.services.ITerminals.TerminalContext;
+import org.eclipse.tcf.te.tcf.terminals.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener;
+import org.eclipse.tcf.te.tcf.terminals.core.internal.tracing.ITraceIds;
+import org.eclipse.tcf.te.tcf.terminals.core.nls.Messages;
+import org.eclipse.tcf.util.TCFTask;
+import org.eclipse.tcf.te.core.async.AsyncCallbackCollector;
+import org.eclipse.tcf.te.runtime.callback.Callback;
+import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
+import org.eclipse.tcf.te.tcf.core.utils.ExceptionUtils;
+
+/**
+ * Remote terminal streams listener implementation.
+ */
+public class TerminalsStreamsListener implements IStreams.StreamsListener, ITerminalsContextAwareListener {
+ // The parent terminals launcher instance
+ private final TerminalsLauncher parent;
+ // The remote terminal context
+ private ITerminals.TerminalContext context;
+ // The list of registered stream data receivers
+ private final List<StreamsDataReceiver> dataReceiver = new ArrayList<StreamsDataReceiver>();
+ // The stream data provider
+ private StreamsDataProvider dataProvider;
+ // The list of delayed stream created events
+ private final List<StreamCreatedEvent> delayedCreatedEvents = new ArrayList<StreamCreatedEvent>();
+ // The list of created runnable's
+ private final List<Runnable> runnables = new ArrayList<Runnable>();
+
+ /**
+ * Immutable stream created event.
+ */
+ private final static class StreamCreatedEvent {
+ /**
+ * The stream type.
+ */
+ public final String streamType;
+ /**
+ * The stream id.
+ */
+ public final String streamId;
+ /**
+ * The context id.
+ */
+ public final String contextId;
+
+ // As the class is immutable, we do not need to build the toString
+ // value again and again. Build it once in the constructor and reuse it later.
+ private final String toString;
+
+ /**
+ * Constructor.
+ *
+ * @param streamType The stream type.
+ * @param streamId The stream id.
+ * @param contextId The context id.
+ */
+ public StreamCreatedEvent(String streamType, String streamId, String contextId) {
+ this.streamType = streamType;
+ this.streamId = streamId;
+ this.contextId = contextId;
+
+ toString = toString();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof StreamCreatedEvent
+ && toString().equals(((StreamCreatedEvent)obj).toString());
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if (toString != null) return toString;
+
+ StringBuilder builder = new StringBuilder(getClass().getSimpleName());
+ builder.append(": streamType = "); //$NON-NLS-1$
+ builder.append(streamType);
+ builder.append("; streamId = "); //$NON-NLS-1$
+ builder.append(streamId);
+ builder.append("; contextId = "); //$NON-NLS-1$
+ builder.append(contextId);
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Remote stream reader runnable implementation.
+ * <p>
+ * The runnable will be executed within a thread and is responsible to read the
+ * incoming data from the associated stream and forward them to the registered receivers.
+ */
+ protected class StreamReaderRunnable implements Runnable {
+ // The associated stream id
+ private final String streamId;
+ // The associated stream type id
+ private final String streamTypeId;
+ // The list of receivers applicable for the associated stream type id
+ private final List<StreamsDataReceiver> receivers = new ArrayList<StreamsDataReceiver>();
+ // The currently active read task
+ private TCFTask<ReadData> activeTask;
+ // The callback to invoke if the runnable stopped
+ private ICallback callback;
+
+ // Flag to stop the runnable
+ private boolean stopped = false;
+
+ /**
+ * Immutable class describing the result returned by {@link StreamReaderRunnable#read(IStreams, String, int)}.
+ */
+ protected class ReadData {
+ /**
+ * The number of lost bytes in case of a buffer overflow. If <code>-1</code>,
+ * an unknown number of bytes were lost. If non-zero and <code>data.length</code> is
+ * non-zero, the lost bytes are considered located right before the read bytes.
+ */
+ public final int lostBytes;
+ /**
+ * The read data as byte array.
+ */
+ public final byte[] data;
+ /**
+ * Flag to signal if the end of the stream has been reached.
+ */
+ public final boolean eos;
+
+ /**
+ * Constructor.
+ */
+ public ReadData(int lostBytes, byte[] data, boolean eos) {
+ this.lostBytes = lostBytes;
+ this.data = data;
+ this.eos = eos;
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param streamId The associated stream id. Must not be <code>null</code>.
+ * @param streamTypeId The associated stream type id. Must not be <code>null</code>.
+ * @param receivers The list of registered data receivers. Must not be <code>null</code>.
+ */
+ public StreamReaderRunnable(String streamId, String streamTypeId, StreamsDataReceiver[] receivers) {
+ Assert.isNotNull(streamId);
+ Assert.isNotNull(streamTypeId);
+ Assert.isNotNull(receivers);
+
+ this.streamId = streamId;
+ this.streamTypeId = streamTypeId;
+
+ // Loop the list of receivers and filter out the applicable ones
+ for (StreamsDataReceiver receiver : receivers) {
+ if (receiver.isApplicable(this.streamTypeId))
+ this.receivers.add(receiver);
+ }
+ }
+
+ /**
+ * Returns the associated stream id.
+ *
+ * @return The associated stream id.
+ */
+ public final String getStreamId() {
+ return streamId;
+ }
+
+ /**
+ * Returns if or if not the list of applicable receivers is empty.
+ *
+ * @return <code>True</code> if the list of applicable receivers is empty, <code>false</code> otherwise.
+ */
+ public final boolean isEmpty() {
+ return receivers.isEmpty();
+ }
+
+ /**
+ * Stop the runnable.
+ *
+ * @param callback The callback to invoke if the runnable stopped.
+ */
+ public final synchronized void stop(ICallback callback) {
+ // If the runnable is stopped already, invoke the callback directly
+ if (stopped) {
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ return;
+ }
+
+ // Store the callback instance
+ this.callback = callback;
+ // Mark the runnable as stopped
+ stopped = true;
+ }
+
+ /**
+ * Returns if the runnable should stop.
+ */
+ protected final synchronized boolean isStopped() {
+ return stopped;
+ }
+
+ /**
+ * Sets the currently active reader task.
+ *
+ * @param task The currently active reader task or <code>null</code>.
+ */
+ protected final synchronized void setActiveTask(TCFTask<ReadData> task) {
+ activeTask = task;
+ }
+
+ /**
+ * Returns the currently active reader task.
+ *
+ * @return The currently active reader task or <code>null</code>.
+ */
+ protected final TCFTask<ReadData> getActiveTask() {
+ return activeTask;
+ }
+
+ /**
+ * Returns the callback instance to invoke.
+ *
+ * @return The callback instance or <code>null</code>.
+ */
+ protected final ICallback getCallback() {
+ return callback;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ // Create a snapshot of the receivers
+ final StreamsDataReceiver[] receivers = this.receivers.toArray(new StreamsDataReceiver[this.receivers.size()]);
+ // Get the service instance from the parent
+ final IStreams svcStreams = getParent().getSvcStreams();
+
+ // Run until stopped and the streams service is available
+ while (!isStopped() && svcStreams != null) {
+ try {
+ ReadData streamData = read(svcStreams, streamId, 1024);
+ if (streamData != null) {
+ // Check if the received data contains some stream data
+ if (streamData.data != null) {
+ // Notify the data receivers about the new received data
+ notifyReceiver(new String(streamData.data), receivers);
+ }
+ // If the end of the stream have been reached --> break out
+ if (streamData.eos) break;
+ }
+ } catch (Exception e) {
+ // An error occurred -> Dump to the error log
+ e = ExceptionUtils.checkAndUnwrapException(e);
+ // Check if the blocking read task got canceled
+ if (!(e instanceof CancellationException)) {
+ // Log the error to the user, might be something serious
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.TerminalsStreamReaderRunnable_error_readFailed, streamId, e.getLocalizedMessage()),
+ e);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+ }
+ // break out of the loop
+ break;
+ }
+ }
+
+ // Disconnect from the stream
+ if (svcStreams != null) {
+ svcStreams.disconnect(streamId, new IStreams.DoneDisconnect() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void doneDisconnect(IToken token, Exception error) {
+ // Disconnect is done, ignore any error, invoke the callback
+ synchronized (this) {
+ if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
+ }
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ });
+ } else {
+ // Invoke the callback directly, if any
+ synchronized (this) {
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ }
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ }
+
+ /**
+ * Reads data from the stream and blocks until some data has been received.
+ *
+ * @param service The streams service. Must not be <code>null</code>.
+ * @param streamId The stream id. Must not be <code>null</code>.
+ * @param size The size of the data to read.
+ *
+ * @return The read data.
+ *
+ * @throws Exception In case the read fails.
+ */
+ protected final ReadData read(final IStreams service, final String streamId, final int size) throws Exception {
+ Assert.isNotNull(service);
+ Assert.isNotNull(streamId);
+ Assert.isTrue(!Protocol.isDispatchThread());
+
+ // Create the task object
+ TCFTask<ReadData> task = new TCFTask<ReadData>(getParent().getChannel()) {
+ @Override
+ public void run() {
+ service.read(streamId, size, new IStreams.DoneRead() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.IStreams.DoneRead#doneRead(org.eclipse.tcf.protocol.IToken, java.lang.Exception, int, byte[], boolean)
+ */
+ @Override
+ public void doneRead(IToken token, Exception error, int lostSize, byte[] data, boolean eos) {
+ if (error == null) done(new ReadData(lostSize, data, eos));
+ else error(error);
+ }
+ });
+ }
+ };
+
+ // Push the task object to the runnable instance
+ setActiveTask(task);
+
+ // Block until some data is received
+ return task.get();
+ }
+
+ /**
+ * Notify the data receiver that some data has been received.
+ *
+ * @param data The data or <code>null</code>.
+ */
+ protected final void notifyReceiver(final String data, final StreamsDataReceiver[] receivers) {
+ if (data == null) return;
+ // Notify the data receiver
+ for (StreamsDataReceiver receiver : receivers) {
+ try {
+ // Get the writer
+ Writer writer = receiver.getWriter();
+ // Append the data
+ writer.append(data);
+ // And flush it
+ writer.flush();
+ } catch (IOException e) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(1, null)) {
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.TerminalsStreamReaderRunnable_error_appendFailed, streamId, data),
+ e);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remote stream writer runnable implementation.
+ * <p>
+ * The runnable will be executed within a thread and is responsible to read the
+ * incoming data from the registered providers and forward them to the associated stream.
+ */
+ protected class StreamWriterRunnable implements Runnable {
+ // The associated stream id
+ /* default */ final String streamId;
+ // The associated stream type id
+ @SuppressWarnings("unused")
+ private final String streamTypeId;
+ // The data provider applicable for the associated stream type id
+ private final StreamsDataProvider provider;
+ // The currently active write task
+ private TCFTask<Object> activeTask;
+ // The callback to invoke if the runnable stopped
+ private ICallback callback;
+
+ // Flag to stop the runnable
+ private boolean stopped = false;
+
+ /**
+ * Constructor.
+ *
+ * @param streamId The associated stream id. Must not be <code>null</code>.
+ * @param streamTypeId The associated stream type id. Must not be <code>null</code>.
+ * @param provider The data provider. Must not be <code>null</code> and must be applicable for the stream type.
+ */
+ public StreamWriterRunnable(String streamId, String streamTypeId, StreamsDataProvider provider) {
+ Assert.isNotNull(streamId);
+ Assert.isNotNull(streamTypeId);
+ Assert.isNotNull(provider);
+ Assert.isTrue(provider.isApplicable(streamTypeId));
+
+ this.streamId = streamId;
+ this.streamTypeId = streamTypeId;
+ this.provider = provider;
+ }
+
+ /**
+ * Returns the associated stream id.
+ *
+ * @return The associated stream id.
+ */
+ public final String getStreamId() {
+ return streamId;
+ }
+
+ /**
+ * Stop the runnable.
+ *
+ * @param callback The callback to invoke if the runnable stopped.
+ */
+ public final synchronized void stop(ICallback callback) {
+ // If the runnable is stopped already, invoke the callback directly
+ if (stopped) {
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ return;
+ }
+
+ // Store the callback instance
+ this.callback = callback;
+ // Mark the runnable as stopped
+ stopped = true;
+ }
+
+ /**
+ * Returns if the runnable should stop.
+ */
+ protected final synchronized boolean isStopped() {
+ return stopped;
+ }
+
+ /**
+ * Sets the currently active writer task.
+ *
+ * @param task The currently active writer task or <code>null</code>.
+ */
+ protected final synchronized void setActiveTask(TCFTask<Object> task) {
+ activeTask = task;
+ }
+
+ /**
+ * Returns the currently active writer task.
+ *
+ * @return The currently active writer task or <code>null</code>.
+ */
+ protected final TCFTask<Object> getActiveTask() {
+ return activeTask;
+ }
+
+ /**
+ * Returns the callback instance to invoke.
+ *
+ * @return The callback instance or <code>null</code>.
+ */
+ protected final ICallback getCallback() {
+ return callback;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ // If not data provider is set, we are done here immediately
+ if (provider == null) {
+ // Invoke the callback directly, if any
+ synchronized (this) {
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ }
+ // Mark the runnable definitely stopped
+ stopped = true;
+
+ return;
+ }
+
+ // Get the service instance from the parent
+ final IStreams svcStreams = getParent().getSvcStreams();
+
+ // Create the data buffer instance
+ final char[] buffer = new char[1024];
+
+ // Run until stopped and the streams service is available
+ while (!isStopped() && svcStreams != null) {
+ try {
+ // Read available data from the data provider
+ int charactersRead = provider.getReader().read(buffer, 0, 1024);
+ // Have we reached the end of the stream -> break out
+ if (charactersRead == -1) break;
+ // If we read some data from the provider, write it to the stream
+ if (charactersRead > 0) write(svcStreams, streamId, new String(buffer).getBytes(), charactersRead);
+ } catch (Exception e) {
+ // An error occurred -> Dump to the error log
+ e = ExceptionUtils.checkAndUnwrapException(e);
+ // Check if the blocking read task got canceled
+ if (!(e instanceof CancellationException)) {
+ // Log the error to the user, might be something serious
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.TerminalsStreamWriterRunnable_error_writeFailed, streamId, e.getLocalizedMessage()),
+ e);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+ }
+ // break out of the loop
+ break;
+ }
+ }
+
+ // 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) {
+ // Disconnect is done, ignore any error, invoke the callback
+ synchronized (this) {
+ if (getCallback() != null) getCallback().done(this, Status.OK_STATUS);
+ }
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ });
+ }
+ });
+ } else {
+ // Invoke the callback directly, if any
+ synchronized (this) {
+ if (callback != null) callback.done(this, Status.OK_STATUS);
+ }
+ // Mark the runnable definitely stopped
+ stopped = true;
+ }
+ }
+
+ /**
+ * Writes data to the stream.
+ *
+ * @param service The streams service. Must not be <code>null</code>.
+ * @param streamId The stream id. Must not be <code>null</code>.
+ * @param data The data buffer. Must not be <code>null</code>.
+ * @param size The size of the data to write.
+ *
+ * @throws Exception In case the write fails.
+ */
+ protected final void write(final IStreams service, final String streamId, final byte[] data, final int size) throws Exception {
+ Assert.isNotNull(service);
+ Assert.isNotNull(streamId);
+ Assert.isTrue(!Protocol.isDispatchThread());
+
+ // Create the task object
+ TCFTask<Object> task = new TCFTask<Object>() {
+ @Override
+ public void run() {
+ service.write(streamId, data, 0, size, new IStreams.DoneWrite() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.IStreams.DoneWrite#doneWrite(org.eclipse.tcf.protocol.IToken, java.lang.Exception)
+ */
+ @Override
+ public void doneWrite(IToken token, Exception error) {
+ if (error == null) done(null);
+ else error(error);
+ }
+ });
+ }
+ };
+ task.get();
+
+ // Push the task object to the runnable instance
+ setActiveTask(task);
+
+ // Execute the write
+ task.get();
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param parent The parent terminals launcher instance. Must not be <code>null</code>
+ */
+ public TerminalsStreamsListener(TerminalsLauncher parent) {
+ Assert.isNotNull(parent);
+ this.parent = parent;
+ }
+
+ /**
+ * Returns the parent terminals launcher instance.
+ *
+ * @return The parent terminals launcher instance.
+ */
+ protected final TerminalsLauncher getParent() {
+ return parent;
+ }
+
+ /**
+ * Dispose the streams listener instance.
+ *
+ * @param callback The callback to invoke if the dispose finished or <code>null</code>.
+ */
+ public void dispose(final ICallback callback) {
+ // Store a final reference to the streams listener instance
+ final IStreams.StreamsListener finStreamsListener = this;
+
+ // Store a final reference to the data receivers list
+ final List<StreamsDataReceiver> finDataReceivers;
+ synchronized (dataReceiver) {
+ finDataReceivers = new ArrayList<StreamsDataReceiver>(dataReceiver);
+ dataReceiver.clear();
+ }
+
+ // Create the callback invocation delegate
+ AsyncCallbackCollector.ICallbackInvocationDelegate delegate = new AsyncCallbackCollector.ICallbackInvocationDelegate() {
+ @Override
+ public void invoke(Runnable runnable) {
+ Assert.isNotNull(runnable);
+ if (Protocol.isDispatchThread()) runnable.run();
+ else Protocol.invokeLater(runnable);
+ }
+ };
+
+ // Create a new collector to catch all runnable stop callback's
+ AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
+ */
+ @Override
+ protected void internalDone(final Object caller, final IStatus status) {
+ Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
+ // Get the service instance from the parent
+ IStreams svcStreams = getParent().getSvcStreams();
+ // Unsubscribe the streams listener from the service
+ svcStreams.unsubscribe(ITerminals.NAME, finStreamsListener, new IStreams.DoneUnsubscribe() {
+ @Override
+ public void doneUnsubscribe(IToken token, Exception error) {
+ // Loop all registered listeners and close them
+ for (StreamsDataReceiver receiver : finDataReceivers) receiver.dispose();
+ // Call the original outer callback
+ if (callback != null) callback.done(caller, status);
+ }
+ });
+ }
+ }, delegate);
+
+ // Loop all runnable's and force them to stop
+ synchronized (runnables) {
+ for (Runnable runnable : runnables) {
+ if (runnable instanceof StreamReaderRunnable) {
+ ((StreamReaderRunnable)runnable).stop(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ }
+ runnables.clear();
+ }
+
+ // Mark the collector initialization done
+ collector.initDone();
+ }
+
+ /**
+ * Adds the given receiver to the stream data receiver list.
+ *
+ * @param receiver The data receiver. Must not be <code>null</code>.
+ */
+ public void registerDataReceiver(StreamsDataReceiver receiver) {
+ Assert.isNotNull(receiver);
+ synchronized (dataReceiver) {
+ if (!dataReceiver.contains(receiver)) dataReceiver.add(receiver);
+ }
+ }
+
+ /**
+ * Removes the given receiver from the stream data receiver list.
+ *
+ * @param receiver The data receiver. Must not be <code>null</code>.
+ */
+ public void unregisterDataReceiver(StreamsDataReceiver receiver) {
+ Assert.isNotNull(receiver);
+ synchronized (dataReceiver) {
+ dataReceiver.remove(receiver);
+ }
+ }
+
+ /**
+ * Sets the stream data provider instance.
+ *
+ * @param provider The stream data provider instance or <code>null</code>.
+ */
+ public void setDataProvider(StreamsDataProvider provider) {
+ dataProvider = provider;
+ }
+
+ /**
+ * Returns the stream data provider instance.
+ *
+ * @return The stream data provider instance or <code>null</code>.
+ */
+ public StreamsDataProvider getDataProvider() {
+ return dataProvider;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener#setTerminalsContext(org.eclipse.tcf.services.ITerminals.TerminalContext)
+ */
+ @Override
+ public void setTerminalsContext(TerminalContext context) {
+ Assert.isNotNull(context);
+ this.context = context;
+
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("Terminals context set to: id='" + context.getID() + "', PTY type='" + context.getPtyType() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_STREAMS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+
+ // Loop all delayed create events and look for the streams for our context
+ synchronized (delayedCreatedEvents) {
+ Iterator<StreamCreatedEvent> iterator = delayedCreatedEvents.iterator();
+ while (iterator.hasNext()) {
+ StreamCreatedEvent event = iterator.next();
+ if (context.getID().equals(event.contextId) || context.getProcessID().equals(event.contextId) || event.contextId == null) {
+ // Re-dispatch the event
+ created(event.streamType, event.streamId, event.contextId);
+ }
+ }
+ // Clear all events
+ delayedCreatedEvents.clear();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.terminals.core.interfaces.launcher.ITerminalsContextAwareListener#getTerminalsContext()
+ */
+ @Override
+ public TerminalContext getTerminalsContext() {
+ return context;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.IStreams.StreamsListener#created(java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void created(String streamType, String streamId, String contextId) {
+ // We ignore any other stream type than ITerminals.NAME
+ if (!ITerminals.NAME.equals(streamType)) return;
+
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("New remote terminals stream created: streamId='" + streamId + "', contextId='" + contextId + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_STREAMS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+
+ // If a terminals context is set, check if the created event is for the
+ // monitored terminals context
+ final ITerminals.TerminalContext context = getTerminalsContext();
+ // The contextId is null if used with an older TCF agent not sending the third parameter
+ //
+ // 2011-10-18: Since the unification of terminals and processes service, the
+ // context id of the streams events is the process context id, not
+ // the terminal context id as before. So check for both here to support
+ // both the older and the newer version of the terminals service.
+ if (context != null && (context.getID().equals(contextId) || context.getProcessID().equals(contextId) || contextId == null)) {
+ // Create a snapshot of the registered data receivers
+ StreamsDataReceiver[] receivers;
+ synchronized (dataReceiver) {
+ receivers = dataReceiver.toArray(new StreamsDataReceiver[dataReceiver.size()]);
+ }
+ // The created event is for the monitored terminals context
+ // --> Create the stream reader thread(s)
+ if (streamId != null && streamId.equals(context.getProperties().get(ITerminals.PROP_STDIN_ID))) {
+ // Data provider set?
+ if (dataProvider != null) {
+ // Create the stdin stream writer runnable
+ StreamWriterRunnable runnable = new StreamWriterRunnable(streamId, ITerminals.PROP_STDIN_ID, dataProvider);
+ // Add to the list of created runnable's
+ synchronized (runnables) { runnables.add(runnable); }
+ // And create and start the thread
+ Thread thread = new Thread(runnable, "Thread-" + ITerminals.PROP_STDIN_ID + "-" + streamId); //$NON-NLS-1$ //$NON-NLS-2$
+ thread.start();
+ }
+ }
+ if (streamId != null && streamId.equals(context.getProperties().get(ITerminals.PROP_STDOUT_ID))) {
+ // Create the stdout stream reader runnable
+ StreamReaderRunnable runnable = new StreamReaderRunnable(streamId, ITerminals.PROP_STDOUT_ID, receivers);
+ // If not empty, create the thread
+ if (!runnable.isEmpty()) {
+ // Add to the list of created runnable's
+ synchronized (runnables) { runnables.add(runnable); }
+ // And create and start the thread
+ Thread thread = new Thread(runnable, "Thread-" + ITerminals.PROP_STDOUT_ID + "-" + streamId); //$NON-NLS-1$ //$NON-NLS-2$
+ thread.start();
+ }
+ }
+ if (streamId != null && streamId.equals(context.getProperties().get(ITerminals.PROP_STDERR_ID))) {
+ // Create the stdout stream reader runnable
+ StreamReaderRunnable runnable = new StreamReaderRunnable(streamId, ITerminals.PROP_STDERR_ID, receivers);
+ // If not empty, create the thread
+ if (!runnable.isEmpty()) {
+ // Add to the list of created runnable's
+ synchronized (runnables) { runnables.add(runnable); }
+ // And create and start the thread
+ Thread thread = new Thread(runnable, "Thread-" + ITerminals.PROP_STDERR_ID + "-" + streamId); //$NON-NLS-1$ //$NON-NLS-2$
+ thread.start();
+ }
+ }
+ } else if (context == null) {
+ // Context not set yet --> add to the delayed list
+ StreamCreatedEvent event = new StreamCreatedEvent(streamType, streamId, contextId);
+ synchronized (delayedCreatedEvents) {
+ if (!delayedCreatedEvents.contains(event)) delayedCreatedEvents.add(event);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.services.IStreams.StreamsListener#disposed(java.lang.String, java.lang.String)
+ */
+ @Override
+ public void disposed(String streamType, String streamId) {
+ // We ignore any other stream type than ITerminals.NAME
+ if (!ITerminals.NAME.equals(streamType)) return;
+
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_STREAMS_LISTENER)) {
+ CoreBundleActivator.getTraceHandler().trace("Remote terminals stream disposed: streamId='" + streamId + "'", //$NON-NLS-1$ //$NON-NLS-2$
+ 0, ITraceIds.TRACE_STREAMS_LISTENER,
+ IStatus.INFO, getClass());
+ }
+
+ // If the delayed created events list is not empty, we have
+ // to check if one of the delayed create events got disposed
+ synchronized (delayedCreatedEvents) {
+ Iterator<StreamCreatedEvent> iterator = delayedCreatedEvents.iterator();
+ while (iterator.hasNext()) {
+ StreamCreatedEvent event = iterator.next();
+ if (event.streamType != null && event.streamType.equals(streamType)
+ && event.streamId != null && event.streamId.equals(streamId)) {
+ // Remove the create event from the list
+ iterator.remove();
+ }
+ }
+ }
+
+ // Stop the thread(s) if the disposed event is for the active
+ // monitored stream id(s).
+ synchronized (runnables) {
+ Iterator<Runnable> iterator = runnables.iterator();
+ while (iterator.hasNext()) {
+ Runnable runnable = iterator.next();
+ if (runnable instanceof StreamReaderRunnable) {
+ StreamReaderRunnable myRunnable = (StreamReaderRunnable)runnable;
+ if (myRunnable.getStreamId().equals(streamId)) {
+ // This method is called within the TCF event dispatch thread, so
+ // we cannot wait for a callback here
+ myRunnable.stop(null);
+ iterator.remove();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.java
new file mode 100644
index 000000000..55e16741c
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2011 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.terminals.core.nls;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Target Explorer TCF terminals extensions core plug-in externalized strings management.
+ */
+public class Messages extends NLS {
+
+ // The plug-in resource bundle name
+ private static final String BUNDLE_NAME = "org.eclipse.tcf.te.tcf.terminals.core.nls.Messages"; //$NON-NLS-1$
+
+ /**
+ * Static constructor.
+ */
+ static {
+ // Load message values from bundle file
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ // **** Declare externalized string id's down here *****
+
+ public static String TerminalsLauncher_error_channelConnectFailed;
+ public static String TerminalsLauncher_error_channelNotConnected;
+ public static String TerminalsLauncher_error_missingProcessPath;
+ public static String TerminalsLauncher_error_missingRequiredService;
+ public static String TerminalsLauncher_error_illegalNullArgument;
+ public static String TerminalsLauncher_error_terminalLaunchFailed;
+ public static String TerminalsLauncher_error_terminalExitFailed;
+ public static String TerminalsLauncher_error_possibleCause;
+ public static String TerminalsLauncher_error_possibleCauseUnknown;
+ public static String TerminalsLauncher_cause_subscribeFailed;
+ public static String TerminalsLauncher_cause_startFailed;
+ public static String TerminalsLauncher_cause_ioexception;
+
+ public static String TerminalsStreamReaderRunnable_error_readFailed;
+ public static String TerminalsStreamWriterRunnable_error_writeFailed;
+ public static String TerminalsStreamReaderRunnable_error_appendFailed;
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.properties b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.properties
new file mode 100644
index 000000000..23a7994c4
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.terminals.core/src/org/eclipse/tcf/te/tcf/terminals/core/nls/Messages.properties
@@ -0,0 +1,20 @@
+#
+# org.eclipse.tcf.te.tcf.terminals.core
+# Externalized Strings.
+#
+
+TerminalsLauncher_error_channelConnectFailed=Failed to connect channel for peer ''{0}''.\n\nPossible Cause:\n{1}
+TerminalsLauncher_error_channelNotConnected=Channel is not connected!
+TerminalsLauncher_error_missingRequiredService=Failed to get required service: {0}!
+TerminalsLauncher_error_illegalNullArgument=Illegal argument: ''{0}'' has to be not null.
+TerminalsLauncher_error_terminalLaunchFailed=Failed to launch remote terminal.\n\Peer: {0}
+TerminalsLauncher_error_terminalExitFailed=Failed to exit remote terminal.\n\nTerminal process id: {0}
+TerminalsLauncher_error_possibleCause=\n\nPossible Cause:\n{0}
+TerminalsLauncher_error_possibleCauseUnknown=\n\nPossible Cause:\nTerminal did not exit for unknown reasons.
+TerminalsLauncher_cause_subscribeFailed=Failed to subscribe to the remote terminal streams service.
+TerminalsLauncher_cause_startFailed=Failed to start remote terminal.
+TerminalsLauncher_cause_ioexception=An IOException occurred.
+
+TerminalsStreamReaderRunnable_error_readFailed=Failed to read data from stream with id ''{0}''.\n\nPossible Cause:\n{1}
+TerminalsStreamWriterRunnable_error_writeFailed=Failed to write data to stream with id ''{0}''.\n\nPossible Cause:\n{1}
+TerminalsStreamReaderRunnable_error_appendFailed=Failed to append data from stream with id ''{0}'' to data receiver.\n\nLost data:\n{1}.

Back to the top