Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Pazderski2020-06-27 12:40:59 +0000
committerPaul Pazderski2020-06-27 13:05:58 +0000
commit00d93c9a6e6af649b84604baa0c0866c4d3511e5 (patch)
tree4c11859cd9c16752e8fbf394bca15efea60485f4
parent154562ad4914da3e6b95fd3381b5e55eb8b2326d (diff)
downloadeclipse.platform.debug-00d93c9a6e6af649b84604baa0c0866c4d3511e5.tar.gz
eclipse.platform.debug-00d93c9a6e6af649b84604baa0c0866c4d3511e5.tar.xz
eclipse.platform.debug-00d93c9a6e6af649b84604baa0c0866c4d3511e5.zip
Bug 558463 - [console] Console redirection changes contentY20200629-1000Y20200629-0740I20200629-1800I20200628-1800
Unfortunately all the existing streams monitoring/proxying/redirection or basically most of the console IO stack is based upon strings. If a program produce 'random' binary output and user configures console to redirect the output to a file it might still be changed due to the (until now) unavoidable string decoding. Keeping existing API and not breaking clients made this change in some parts more complex than it could be. Change-Id: Id8e8fa1f777f06220af86dd4f026873c6b0b2d7b Signed-off-by: Paul Pazderski <paul-eclipse@ppazderski.de>
-rw-r--r--org.eclipse.debug.core/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/IBinaryStreamListener.java38
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamMonitor.java64
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamsProxy.java93
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/model/IFlushableStreamMonitor.java2
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamMonitor.java1
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamsProxy.java1
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/internal/core/InputStreamMonitor.java47
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/internal/core/NullStreamsProxy.java50
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/internal/core/OutputStreamMonitor.java232
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamDecoder.java108
-rw-r--r--org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamsProxy.java30
-rw-r--r--org.eclipse.debug.core/pom.xml2
-rw-r--r--org.eclipse.debug.tests/META-INF/MANIFEST.MF2
-rw-r--r--org.eclipse.debug.tests/pom.xml2
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java74
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/TestUtil.java37
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/InputStreamMonitorTests.java153
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/OutputStreamMonitorTests.java198
-rw-r--r--org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java124
-rw-r--r--org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java162
-rw-r--r--org.eclipse.ui.console/META-INF/MANIFEST.MF3
-rw-r--r--org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleInputStream.java12
-rw-r--r--org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleOutputStream.java2
-rw-r--r--org.eclipse.ui.console/src/org/eclipse/ui/internal/console/StreamDecoder.java86
25 files changed, 1258 insertions, 269 deletions
diff --git a/org.eclipse.debug.core/META-INF/MANIFEST.MF b/org.eclipse.debug.core/META-INF/MANIFEST.MF
index b27c877d9..480148cba 100644
--- a/org.eclipse.debug.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.debug.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.debug.core; singleton:=true
-Bundle-Version: 3.15.200.qualifier
+Bundle-Version: 3.16.0.qualifier
Bundle-ClassPath: .
Bundle-Activator: org.eclipse.debug.core.DebugPlugin
Bundle-Vendor: %providerName
@@ -12,7 +12,7 @@ Export-Package: org.eclipse.debug.core,
org.eclipse.debug.core.model,
org.eclipse.debug.core.sourcelookup,
org.eclipse.debug.core.sourcelookup.containers,
- org.eclipse.debug.internal.core;x-friends:="org.eclipse.debug.ui,org.eclipse.debug.tests,org.eclipse.debug.examples.mixedmode,org.eclipse.jdt.launching",
+ org.eclipse.debug.internal.core;x-friends:="org.eclipse.debug.ui,org.eclipse.debug.tests,org.eclipse.debug.examples.mixedmode,org.eclipse.jdt.launching,org.eclipse.ui.console",
org.eclipse.debug.internal.core.commands;x-friends:="org.eclipse.debug.ui",
org.eclipse.debug.internal.core.groups;x-friends:="org.eclipse.debug.ui",
org.eclipse.debug.internal.core.groups.observer;x-internal:=true,
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/IBinaryStreamListener.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/IBinaryStreamListener.java
new file mode 100644
index 000000000..b21697abe
--- /dev/null
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/IBinaryStreamListener.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Paul Pazderski and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.core;
+
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
+
+/**
+ * A stream listener is notified of changes to a binary stream monitor.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see IBinaryStreamMonitor
+ * @see IStreamListener
+ * @since 3.16
+ */
+public interface IBinaryStreamListener {
+
+ /**
+ * Notifies this listener that data has been appended to the given stream
+ * monitor.
+ *
+ * @param data the content appended; not <code>null</code>
+ * @param monitor the stream monitor to which content was appended
+ */
+ void streamAppended(byte[] data, IBinaryStreamMonitor monitor);
+}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamMonitor.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamMonitor.java
new file mode 100644
index 000000000..1fe05444b
--- /dev/null
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamMonitor.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Paul Pazderski and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.core.model;
+
+import org.eclipse.debug.core.IBinaryStreamListener;
+
+/**
+ * A variant of {@link IStreamMonitor} which does not touch the received content
+ * and pass it as bytes instead of strings.
+ * <p>
+ * A stream monitor manages the contents of the stream a process is writing to,
+ * and notifies registered listeners of changes in the stream.
+ * </p>
+ * <p>
+ * Clients may implement this interface. Generally, a client that provides an
+ * implementation of the {@link IBinaryStreamsProxy} interface must also provide
+ * an implementation of this interface.
+ * </p>
+ *
+ * @see org.eclipse.debug.core.model.IStreamsProxy
+ * @see org.eclipse.debug.core.model.IFlushableStreamMonitor
+ * @since 3.16
+ */
+public interface IBinaryStreamMonitor extends IFlushableStreamMonitor {
+ /**
+ * Adds the given listener to this stream monitor's registered listeners.
+ * Has no effect if an identical listener is already registered.
+ *
+ * @param listener the listener to add
+ */
+ void addBinaryListener(IBinaryStreamListener listener);
+
+ /**
+ * Returns the entire current contents of the stream. An empty array is
+ * returned if the stream is empty.
+ * <p>
+ * Note: the current content is influenced by the buffering mechanism.
+ * </p>
+ *
+ * @return the stream contents as array
+ * @see #isBuffered()
+ * @see #flushContents()
+ */
+ byte[] getData();
+
+ /**
+ * Removes the given listener from this stream monitor's registered listeners.
+ * Has no effect if the listener is not already registered.
+ *
+ * @param listener the listener to remove
+ */
+ void removeBinaryListener(IBinaryStreamListener listener);
+}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamsProxy.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamsProxy.java
new file mode 100644
index 000000000..d355e26c7
--- /dev/null
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IBinaryStreamsProxy.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Paul Pazderski and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.core.model;
+
+import java.io.IOException;
+
+/**
+ * A variant of {@link IStreamsProxy} which does not touch the proxied content
+ * and pass it as bytes instead of strings.
+ * <p>
+ * A streams proxy acts as proxy between the streams of a process and interested
+ * clients. This abstraction allows implementations of <code>IProcess</code> to
+ * handle I/O related to the standard input, output, and error streams
+ * associated with a process.
+ * </p>
+ * <p>
+ * Clients implementing the <code>IProcess</code> interface for a process which
+ * produce or consumes binary content should consider to implement this
+ * interface instead of just {@link IStreamsProxy}.
+ * </p>
+ *
+ * @see IProcess
+ * @see IStreamsProxy
+ * @since 3.16
+ */
+public interface IBinaryStreamsProxy extends IStreamsProxy2 {
+ /**
+ * Returns a monitor for the error stream of this proxy's process, or
+ * <code>null</code> if not supported.
+ * <p>
+ * The monitor is connected to the error stream of the associated process.
+ * </p>
+ * <p>
+ * In contrast to {@link #getErrorStreamMonitor()} which will decode the
+ * stream content to strings, the {@link IBinaryStreamMonitor} will provide
+ * the raw stream data.
+ * </p>
+ *
+ * @return an error stream monitor, or <code>null</code> if none
+ */
+ IBinaryStreamMonitor getBinaryErrorStreamMonitor();
+
+ /**
+ * Returns a monitor for the output stream of this proxy's process, or
+ * <code>null</code> if not supported.
+ * <p>
+ * The monitor is connected to the output stream of the associated process.
+ * </p>
+ * <p>
+ * In contrast to {@link #getOutputStreamMonitor()} which will decode the
+ * stream content to strings, the {@link IBinaryStreamMonitor} will provide
+ * the raw stream data.
+ * </p>
+ *
+ * @return an output stream monitor, or <code>null</code> if none
+ */
+ IBinaryStreamMonitor getBinaryOutputStreamMonitor();
+
+ /**
+ * Writes the given data to the output stream connected to the standard
+ * input stream of this proxy's process.
+ *
+ * @param data the data to be written
+ * @exception IOException when an error occurs writing to the underlying
+ * <code>OutputStream</code>.
+ */
+ default void write(byte[] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ /**
+ * Writes the given data to the output stream connected to the standard
+ * input stream of this proxy's process.
+ *
+ * @param data the data to be written
+ * @param offset start offset in the data
+ * @param length number of bytes to write
+ * @exception IOException when an error occurs writing to the underlying
+ * <code>OutputStream</code>.
+ */
+ void write(byte[] data, int offset, int length) throws IOException;
+}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IFlushableStreamMonitor.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IFlushableStreamMonitor.java
index e0c309ac5..651fbf5ae 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IFlushableStreamMonitor.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IFlushableStreamMonitor.java
@@ -22,6 +22,8 @@ package org.eclipse.debug.core.model;
* Clients may implement this interface.
* </p>
* @since 2.1
+ * @see IStreamMonitor
+ * @see IBinaryStreamMonitor
*/
public interface IFlushableStreamMonitor extends IStreamMonitor {
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamMonitor.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamMonitor.java
index aa26d7acb..8a5677e37 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamMonitor.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamMonitor.java
@@ -27,6 +27,7 @@ import org.eclipse.debug.core.IStreamListener;
* </p>
* @see org.eclipse.debug.core.model.IStreamsProxy
* @see org.eclipse.debug.core.model.IFlushableStreamMonitor
+ * @see org.eclipse.debug.core.model.IBinaryStreamMonitor
*/
public interface IStreamMonitor {
/**
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamsProxy.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamsProxy.java
index 83e5755b2..72d00a2fb 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamsProxy.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/IStreamsProxy.java
@@ -27,6 +27,7 @@ import java.io.IOException;
* provide an implementation of this interface.
* </p>
* @see IProcess
+ * @see IBinaryStreamsProxy
*/
public interface IStreamsProxy {
/**
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/InputStreamMonitor.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/InputStreamMonitor.java
index ef77a0610..7ed21a6ea 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/InputStreamMonitor.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/InputStreamMonitor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Paul Pazderski - Bug 558463: add handling of raw stream content instead of strings
*******************************************************************************/
package org.eclipse.debug.internal.core;
@@ -22,11 +23,10 @@ import java.util.Vector;
import org.eclipse.debug.core.DebugPlugin;
/**
- * Writes to the input stream of a system process,
- * queueing output if the stream is blocked.
+ * Writes to the input stream of a system process, queuing output if the stream
+ * is blocked.
*
- * The input stream monitor writes to system in via
- * an output stream.
+ * The input stream monitor writes to system in via an output stream.
*/
public class InputStreamMonitor {
@@ -34,14 +34,17 @@ public class InputStreamMonitor {
* The stream which is being written to (connected to system in).
*/
private OutputStream fStream;
+
/**
* The queue of output.
*/
- private Vector<String> fQueue;
+ private Vector<byte[]> fQueue;
+
/**
* The thread which writes to the stream.
*/
private Thread fThread;
+
/**
* A lock for ensuring that writes to the queue are contiguous
*/
@@ -101,8 +104,25 @@ public class InputStreamMonitor {
* @param text text to append
*/
public void write(String text) {
- synchronized(fLock) {
- fQueue.add(text);
+ synchronized (fLock) {
+ fQueue.add(fCharset == null ? text.getBytes() : text.getBytes(fCharset));
+ fLock.notifyAll();
+ }
+ }
+
+ /**
+ * Appends the given binary data to the stream, or queues the text to be
+ * written at a later time if the stream is blocked.
+ *
+ * @param data data to append; not <code>null</code>
+ * @param offset start offset in data
+ * @param length number of bytes in data
+ */
+ public void write(byte[] data, int offset, int length) {
+ synchronized (fLock) {
+ byte[] copy = new byte[length];
+ System.arraycopy(data, offset, copy, 0, length);
+ fQueue.add(copy);
fLock.notifyAll();
}
}
@@ -151,14 +171,10 @@ public class InputStreamMonitor {
*/
protected void writeNext() {
while (!fQueue.isEmpty() && !fClosed) {
- String text = fQueue.firstElement();
+ byte[] data = fQueue.firstElement();
fQueue.removeElementAt(0);
try {
- if (fCharset != null) {
- fStream.write(text.getBytes(fCharset));
- } else {
- fStream.write(text.getBytes());
- }
+ fStream.write(data);
fStream.flush();
} catch (IOException e) {
DebugPlugin.log(e);
@@ -180,7 +196,8 @@ public class InputStreamMonitor {
* Closes the output stream attached to the standard input stream of this
* monitor's process.
*
- * @exception IOException if an exception occurs closing the input stream
+ * @exception IOException if an exception occurs closing the input stream or
+ * stream is already closed
*/
public void closeInputStream() throws IOException {
if (!fClosed) {
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/NullStreamsProxy.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/NullStreamsProxy.java
index 0bbcdf62e..494a9c297 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/NullStreamsProxy.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/NullStreamsProxy.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -16,11 +16,13 @@ package org.eclipse.debug.internal.core;
import java.io.IOException;
import java.io.InputStream;
+import org.eclipse.debug.core.IBinaryStreamListener;
import org.eclipse.debug.core.IStreamListener;
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
+import org.eclipse.debug.core.model.IBinaryStreamsProxy;
import org.eclipse.debug.core.model.IStreamMonitor;
-import org.eclipse.debug.core.model.IStreamsProxy2;
-public class NullStreamsProxy implements IStreamsProxy2 {
+public class NullStreamsProxy implements IBinaryStreamsProxy {
private NullStreamMonitor outputStreamMonitor;
private NullStreamMonitor errorStreamMonitor;
@@ -48,7 +50,21 @@ public class NullStreamsProxy implements IStreamsProxy2 {
public void write(String input) throws IOException {
}
- private class NullStreamMonitor implements IStreamMonitor {
+ @Override
+ public IBinaryStreamMonitor getBinaryErrorStreamMonitor() {
+ return errorStreamMonitor;
+ }
+
+ @Override
+ public IBinaryStreamMonitor getBinaryOutputStreamMonitor() {
+ return outputStreamMonitor;
+ }
+
+ @Override
+ public void write(byte[] data, int offset, int length) throws IOException {
+ }
+
+ private class NullStreamMonitor implements IBinaryStreamMonitor {
private InputStream fStream;
public NullStreamMonitor(InputStream stream) {
@@ -81,7 +97,33 @@ public class NullStreamsProxy implements IStreamsProxy2 {
}
@Override
+ public void flushContents() {
+ }
+
+ @Override
+ public void setBuffered(boolean buffer) {
+ }
+
+ @Override
+ public boolean isBuffered() {
+ return false;
+ }
+
+ @Override
public void removeListener(IStreamListener listener) {
}
+
+ @Override
+ public void addBinaryListener(IBinaryStreamListener listener) {
+ }
+
+ @Override
+ public byte[] getData() {
+ return new byte[0];
+ }
+
+ @Override
+ public void removeBinaryListener(IBinaryStreamListener listener) {
+ }
}
}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/OutputStreamMonitor.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/OutputStreamMonitor.java
index 4c64c2654..916858aab 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/OutputStreamMonitor.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/OutputStreamMonitor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -11,13 +11,14 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Paul Pazderski - Bug 545769: fixed rare UTF-8 character corruption bug
+ * Paul Pazderski - Bug 558463: add handling of raw stream content instead of strings
*******************************************************************************/
package org.eclipse.debug.internal.core;
import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -25,58 +26,69 @@ import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.IBinaryStreamListener;
import org.eclipse.debug.core.IStreamListener;
-import org.eclipse.debug.core.model.IFlushableStreamMonitor;
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
/**
- * Monitors the output stream of a system process and notifies
- * listeners of additions to the stream.
- *
- * The output stream monitor reads system out (or err) via
- * and input stream.
+ * Monitors the output stream of a system process and notifies listeners of
+ * additions to the stream.
+ * <p>
+ * The output stream monitor reads system out (or err) via and input stream.
*/
-public class OutputStreamMonitor implements IFlushableStreamMonitor {
+public class OutputStreamMonitor implements IBinaryStreamMonitor {
+ /**
+ * The size of the read buffer.
+ */
+ private static final int BUFFER_SIZE = 8192;
+
/**
* The stream being monitored (connected system out or err).
*/
private InputStream fStream;
/**
- * A collection of listeners
+ * A collection of listeners interested in decoded content.
*/
private ListenerList<IStreamListener> fListeners = new ListenerList<>();
/**
- * Whether content is being buffered
+ * A collection of listeners interested in the raw content.
*/
- private boolean fBuffered = true;
+ private ListenerList<IBinaryStreamListener> fBinaryListeners = new ListenerList<>();
/**
- * The local copy of the stream contents
+ * The buffered stream content since last flush. Value of <code>null</code>
+ * indicates disabled buffering.
+ *
+ * @see #isBuffered()
*/
- private StringBuilder fContents;
+ private ByteArrayOutputStream fContents;
/**
- * The thread which reads from the stream
+ * Decoder used for the buffered content. This is required to keep the state
+ * of an incomplete character.
*/
- private Thread fThread;
+ private StreamDecoder fBufferedDecoder;
+ private String fCachedDecodedContents;
/**
- * The size of the read buffer
+ * The thread which reads from the stream
*/
- private static final int BUFFER_SIZE= 8192;
+ private Thread fThread;
/**
- * Whether or not this monitor has been killed.
- * When the monitor is killed, it stops reading
- * from the stream immediately.
+ * Whether or not this monitor has been killed. When the monitor is killed,
+ * it stops reading from the stream immediately.
*/
- private boolean fKilled= false;
+ private boolean fKilled = false;
private long lastSleep;
private Charset fCharset;
+ private StreamDecoder fDecoder;
+
private final AtomicBoolean fDone;
/**
@@ -84,13 +96,15 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
* out or err).
*
* @param stream input stream to read from
- * @param charset stream charset or <code>null</code> for system default
+ * @param charset stream charset or <code>null</code> for system default;
+ * unused if only the binary interface is used
*/
public OutputStreamMonitor(InputStream stream, Charset charset) {
fStream = new BufferedInputStream(stream, 8192);
fCharset = charset;
- fContents= new StringBuilder();
+ fDecoder = new StreamDecoder(charset == null ? Charset.defaultCharset() : charset);
fDone = new AtomicBoolean(false);
+ setBuffered(true);
}
/**
@@ -112,35 +126,97 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
fListeners.add(listener);
}
+ @Override
+ public synchronized void addBinaryListener(IBinaryStreamListener listener) {
+ fBinaryListeners.add(listener);
+ }
+
/**
- * Causes the monitor to close all
- * communications between it and the
+ * Causes the monitor to close all communications between it and the
* underlying stream by waiting for the thread to terminate.
*/
protected void close() {
if (fThread != null) {
- Thread thread= fThread;
- fThread= null;
+ Thread thread = fThread;
+ fThread = null;
try {
thread.join();
} catch (InterruptedException ie) {
}
fListeners = new ListenerList<>();
+ fBinaryListeners = new ListenerList<>();
}
}
/**
- * Notifies the listeners that text has
- * been appended to the stream.
- * @param text the text that was appended to the stream
+ * Notifies the listeners that content has been appended to the stream. Will
+ * notify both, binary and text listeners.
+ *
+ * @param data that has been appended; not <code>null</code>
+ * @param offset start of valid data
+ * @param length number of valid bytes
*/
- private void fireStreamAppended(String text) {
- getNotifier().notifyAppend(text);
+ private void fireStreamAppended(final byte[] data, int offset, int length) {
+ if (!fListeners.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ fDecoder.decode(sb, data, offset, length);
+ final String text = sb.toString();
+ for (final IStreamListener listener : fListeners) {
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void run() throws Exception {
+ listener.streamAppended(text, OutputStreamMonitor.this);
+ }
+
+ @Override
+ public void handleException(Throwable exception) {
+ DebugPlugin.log(exception);
+ }
+ });
+ }
+ }
+ if (!fBinaryListeners.isEmpty()) {
+ final byte[] validData;
+ if (offset > 0 || length < data.length) {
+ validData = new byte[length];
+ System.arraycopy(data, offset, validData, 0, length);
+ } else {
+ validData = data;
+ }
+ for (final IBinaryStreamListener listener : fBinaryListeners) {
+ SafeRunner.run(new ISafeRunnable() {
+ @Override
+ public void run() throws Exception {
+ listener.streamAppended(validData, OutputStreamMonitor.this);
+ }
+
+ @Override
+ public void handleException(Throwable exception) {
+ DebugPlugin.log(exception);
+ }
+ });
+ }
+ }
}
@Override
public synchronized String getContents() {
- return fContents.toString();
+ if (!isBuffered()) {
+ return ""; //$NON-NLS-1$
+ }
+ if (fCachedDecodedContents != null) {
+ return fCachedDecodedContents;
+ }
+ StringBuilder sb = new StringBuilder();
+ byte[] data = getData();
+ fBufferedDecoder.decode(sb, data, 0, data.length);
+ fCachedDecodedContents = sb.toString();
+ return fCachedDecodedContents;
+ }
+
+ @Override
+ public synchronized byte[] getData() {
+ return isBuffered() ? fContents.toByteArray() : new byte[0];
}
private void read() {
@@ -154,30 +230,29 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
/**
* Continually reads from the stream.
* <p>
- * This method, along with the <code>startReading</code>
- * method is used to allow <code>OutputStreamMonitor</code>
- * to implement <code>Runnable</code> without publicly
- * exposing a <code>run</code> method.
+ * This method, along with the {@link #startMonitoring()} method is used to
+ * allow {@link OutputStreamMonitor} to implement {@link Runnable} without
+ * publicly exposing a {@link Runnable#run()} method.
*/
private void internalRead() {
lastSleep = System.currentTimeMillis();
long currentTime = lastSleep;
- char[] chars = new char[BUFFER_SIZE];
+ byte[] buffer = new byte[BUFFER_SIZE];
int read = 0;
- try (InputStreamReader reader = (fCharset == null ? new InputStreamReader(fStream) : new InputStreamReader(fStream, fCharset))) {
+ try {
while (read >= 0) {
try {
if (fKilled) {
break;
}
- read = reader.read(chars);
+ read = fStream.read(buffer);
if (read > 0) {
- String text = new String(chars, 0, read);
synchronized (this) {
if (isBuffered()) {
- fContents.append(text);
+ fCachedDecodedContents = null;
+ fContents.write(buffer, 0, read);
}
- fireStreamAppended(text);
+ fireStreamAppended(buffer, 0, read);
}
}
} catch (IOException ioe) {
@@ -204,13 +279,17 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
}
}
}
- } catch (IOException e) {
- DebugPlugin.log(e);
+ } finally {
+ try {
+ fStream.close();
+ } catch (IOException e) {
+ DebugPlugin.log(e);
+ }
}
}
protected void kill() {
- fKilled= true;
+ fKilled = true;
}
@Override
@@ -218,6 +297,11 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
fListeners.remove(listener);
}
+ @Override
+ public synchronized void removeBinaryListener(IBinaryStreamListener listener) {
+ fBinaryListeners.remove(listener);
+ }
+
/**
* Starts a thread which reads from the stream
*/
@@ -233,57 +317,37 @@ public class OutputStreamMonitor implements IFlushableStreamMonitor {
@Override
public synchronized void setBuffered(boolean buffer) {
- fBuffered = buffer;
+ if (isBuffered() != buffer) {
+ fCachedDecodedContents = null;
+ if (buffer) {
+ fContents = new ByteArrayOutputStream();
+ fBufferedDecoder = new StreamDecoder(fCharset == null ? Charset.defaultCharset() : fCharset);
+ } else {
+ fContents = null;
+ fBufferedDecoder = null;
+ }
+ }
}
@Override
public synchronized void flushContents() {
- fContents.setLength(0);
+ if (isBuffered()) {
+ fCachedDecodedContents = null;
+ fContents.reset();
+ }
}
@Override
public synchronized boolean isBuffered() {
- return fBuffered;
- }
-
- private ContentNotifier getNotifier() {
- return new ContentNotifier();
+ return fContents != null;
}
/**
* @return {@code true} if reading the underlying stream is done.
- * {@code false} if reading the stream has not started or is not done.
+ * {@code false} if reading the stream has not started or is not
+ * done.
*/
public boolean isReadingDone() {
return fDone.get();
}
-
- class ContentNotifier implements ISafeRunnable {
-
- private IStreamListener fListener;
- private String fText;
-
- @Override
- public void handleException(Throwable exception) {
- DebugPlugin.log(exception);
- }
-
- @Override
- public void run() throws Exception {
- fListener.streamAppended(fText, OutputStreamMonitor.this);
- }
-
- public void notifyAppend(String text) {
- if (text == null) {
- return;
- }
- fText = text;
- for (IStreamListener iStreamListener : fListeners) {
- fListener = iStreamListener;
- SafeRunner.run(this);
- }
- fListener = null;
- fText = null;
- }
- }
}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamDecoder.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamDecoder.java
new file mode 100644
index 000000000..7cb15a736
--- /dev/null
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamDecoder.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Andreas Loth and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andreas Loth - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.debug.internal.core;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Wraps CharsetDecoder to decode a byte stream statefully to characters.
+ *
+ * @since 3.7 org.eclipse.ui.console
+ */
+public class StreamDecoder {
+ // For more context see https://bugs.eclipse.org/bugs/show_bug.cgi?id=507664
+
+ static private final int BUFFER_SIZE = 4096;
+
+ private final CharsetDecoder decoder;
+ private final ByteBuffer inputBuffer;
+ private final CharBuffer outputBuffer;
+ private boolean finished;
+
+ public StreamDecoder(Charset charset) {
+ this.decoder = charset.newDecoder();
+ this.decoder.onMalformedInput(CodingErrorAction.REPLACE);
+ this.decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ this.inputBuffer = ByteBuffer.allocate(StreamDecoder.BUFFER_SIZE);
+ this.inputBuffer.flip();
+ this.outputBuffer = CharBuffer.allocate(StreamDecoder.BUFFER_SIZE);
+ this.finished = false;
+ }
+
+ private void consume(StringBuilder consumer) {
+ this.outputBuffer.flip();
+ consumer.append(this.outputBuffer);
+ this.outputBuffer.clear();
+ }
+
+ private void internalDecode(StringBuilder consumer, byte[] buffer, int offset, int length) {
+ assert (offset >= 0);
+ assert (length >= 0);
+ int position = offset;
+ int end = offset + length;
+ assert (end <= buffer.length);
+ boolean finishedReading = false;
+ do {
+ CoderResult result = this.decoder.decode(this.inputBuffer, this.outputBuffer, false);
+ if (result.isOverflow()) {
+ this.consume(consumer);
+ } else if (result.isUnderflow()) {
+ this.inputBuffer.compact();
+ int remaining = this.inputBuffer.remaining();
+ assert (remaining > 0);
+ int read = Math.min(remaining, end - position);
+ if (read > 0) {
+ this.inputBuffer.put(buffer, position, read);
+ position += read;
+ } else {
+ finishedReading = true;
+ }
+ this.inputBuffer.flip();
+ } else {
+ assert false;
+ }
+ } while (!finishedReading);
+ }
+
+ public void decode(StringBuilder consumer, byte[] buffer, int offset, int length) {
+ this.internalDecode(consumer, buffer, offset, length);
+ this.consume(consumer);
+ }
+
+ public void finish(StringBuilder consumer) {
+ if (this.finished) {
+ return;
+ }
+ this.finished = true;
+ CoderResult result;
+ result = this.decoder.decode(this.inputBuffer, this.outputBuffer, true);
+ assert (result.isOverflow() || result.isUnderflow());
+ do {
+ result = this.decoder.flush(this.outputBuffer);
+ if (result.isOverflow()) {
+ this.consume(consumer);
+ } else {
+ assert result.isUnderflow();
+ }
+ } while (!result.isUnderflow());
+ this.consume(consumer);
+ }
+
+}
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamsProxy.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamsProxy.java
index f8aa4ef0d..0f14b7d1a 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamsProxy.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/StreamsProxy.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -17,15 +17,19 @@ package org.eclipse.debug.internal.core;
import java.io.IOException;
import java.nio.charset.Charset;
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
+import org.eclipse.debug.core.model.IBinaryStreamsProxy;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.debug.core.model.IStreamsProxy2;
/**
- * Standard implementation of a streams proxy for IStreamsProxy.
+ * Standard implementation of a streams proxy for {@link IStreamsProxy},
+ * {@link IStreamsProxy2} and {@link IBinaryStreamsProxy}.
+ * <p>
+ * Will use the same monitor instances for binary and string stream handling.
*/
-
-public class StreamsProxy implements IStreamsProxy, IStreamsProxy2 {
+public class StreamsProxy implements IBinaryStreamsProxy {
/**
* The monitor for the output stream (connected to standard out of the process)
*/
@@ -155,4 +159,22 @@ public class StreamsProxy implements IStreamsProxy, IStreamsProxy2 {
}
+ @Override
+ public IBinaryStreamMonitor getBinaryErrorStreamMonitor() {
+ return fErrorMonitor;
+ }
+
+ @Override
+ public IBinaryStreamMonitor getBinaryOutputStreamMonitor() {
+ return fOutputMonitor;
+ }
+
+ @Override
+ public void write(byte[] data, int offset, int length) throws IOException {
+ if (!isClosed(false)) {
+ fInputMonitor.write(data, offset, length);
+ } else {
+ throw new IOException();
+ }
+ }
}
diff --git a/org.eclipse.debug.core/pom.xml b/org.eclipse.debug.core/pom.xml
index 7b1e4eeec..55df17e89 100644
--- a/org.eclipse.debug.core/pom.xml
+++ b/org.eclipse.debug.core/pom.xml
@@ -18,6 +18,6 @@
</parent>
<groupId>org.eclipse.debug</groupId>
<artifactId>org.eclipse.debug.core</artifactId>
- <version>3.15.200-SNAPSHOT</version>
+ <version>3.16.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/org.eclipse.debug.tests/META-INF/MANIFEST.MF b/org.eclipse.debug.tests/META-INF/MANIFEST.MF
index fa85ae812..2f725b167 100644
--- a/org.eclipse.debug.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.debug.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.debug.tests;singleton:=true
-Bundle-Version: 3.11.700.qualifier
+Bundle-Version: 3.11.800.qualifier
Bundle-Activator: org.eclipse.debug.tests.TestsPlugin
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui;bundle-version="[3.6.0,4.0.0)",
diff --git a/org.eclipse.debug.tests/pom.xml b/org.eclipse.debug.tests/pom.xml
index 97b29b5d1..5e20ea247 100644
--- a/org.eclipse.debug.tests/pom.xml
+++ b/org.eclipse.debug.tests/pom.xml
@@ -18,7 +18,7 @@
</parent>
<groupId>org.eclipse.debug</groupId>
<artifactId>org.eclipse.debug.tests</artifactId>
- <version>3.11.700-SNAPSHOT</version>
+ <version>3.11.800-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
<properties>
<code.ignoredWarnings>${tests.ignoredWarnings}</code.ignoredWarnings>
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
index ed53aad4a..627fd312d 100644
--- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
@@ -21,6 +21,8 @@ import org.eclipse.debug.tests.console.ConsoleManagerTests;
import org.eclipse.debug.tests.console.ConsoleTests;
import org.eclipse.debug.tests.console.IOConsoleFixedWidthTests;
import org.eclipse.debug.tests.console.IOConsoleTests;
+import org.eclipse.debug.tests.console.InputStreamMonitorTests;
+import org.eclipse.debug.tests.console.OutputStreamMonitorTests;
import org.eclipse.debug.tests.console.ProcessConsoleManagerTests;
import org.eclipse.debug.tests.console.ProcessConsoleTests;
import org.eclipse.debug.tests.console.RuntimeProcessTests;
@@ -59,23 +61,63 @@ import org.junit.runners.Suite;
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
- SourceLookupFacilityTests.class, BreakpointOrderingTests.class,
- VirtualViewerDeltaTests.class, VirtualViewerContentTests.class,
- VirtualViewerLazyModeTests.class, VirtualViewerSelectionTests.class,
- VirtualViewerStateTests.class, VirtualViewerUpdateTests.class,
- VirtualViewerFilterTests.class, FilterTransformTests.class,
- ChildrenUpdateTests.class, PresentationContextTests.class,
- MemoryRenderingTests.class, LaunchConfigurationTests.class,
- AcceleratorSubstitutionTests.class, LaunchHistoryTests.class,
- LaunchFavoriteTests.class, LaunchManagerTests.class,
- RefreshTabTests.class, ArgumentParsingTests.class, LaunchTests.class,
+ // Source lookup tests
+ SourceLookupFacilityTests.class,
+ // BP tests
+ BreakpointOrderingTests.class,
+ // Note: jface viewer tests were moved out of nightly tests
+ // due to frequent problems on nightly build machines.
+ // (Bug 343308).
+
+ // Virtual viewer tests
+ VirtualViewerDeltaTests.class,
+ VirtualViewerContentTests.class,
+ VirtualViewerLazyModeTests.class,
+ VirtualViewerSelectionTests.class,
+ VirtualViewerStateTests.class,
+ VirtualViewerUpdateTests.class,
+ VirtualViewerFilterTests.class,
+
+ // Viewer neutral tests
+ FilterTransformTests.class,
+ ChildrenUpdateTests.class,
+ PresentationContextTests.class,
+
+ // Memory view
+ MemoryRenderingTests.class,
+
+ // Launch framework
+ LaunchConfigurationTests.class,
+ AcceleratorSubstitutionTests.class,
+ LaunchHistoryTests.class,
+ LaunchFavoriteTests.class,
+ LaunchManagerTests.class,
+ RefreshTabTests.class,
+ ArgumentParsingTests.class,
+ LaunchTests.class,
+
+ // Status handlers
StatusHandlerTests.class,
+
+ // Step filters
StepFiltersTests.class,
- ConsoleDocumentAdapterTests.class, ConsoleManagerTests.class,
- ConsoleTests.class, IOConsoleTests.class,
- IOConsoleFixedWidthTests.class, ProcessConsoleManagerTests.class,
- ProcessConsoleTests.class, StreamsProxyTests.class,
- TextConsoleViewerTest.class, RuntimeProcessTests.class,
- LaunchGroupTests.class })
+
+ // Console view
+ ConsoleDocumentAdapterTests.class,
+ ConsoleManagerTests.class,
+ ConsoleTests.class,
+ IOConsoleTests.class,
+ IOConsoleFixedWidthTests.class,
+ ProcessConsoleManagerTests.class,
+ ProcessConsoleTests.class,
+ StreamsProxyTests.class,
+ TextConsoleViewerTest.class,
+ RuntimeProcessTests.class,
+ OutputStreamMonitorTests.class,
+ InputStreamMonitorTests.class,
+
+ // Launch Groups
+ LaunchGroupTests.class,
+})
public class AutomatedSuite {
}
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/TestUtil.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/TestUtil.java
index 99f2a2645..e7644a8c1 100644
--- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/TestUtil.java
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/TestUtil.java
@@ -24,6 +24,8 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Supplier;
+
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
@@ -108,8 +110,8 @@ public class TestUtil {
* background thread, just waits.
*
* @param <T> type of the context
- * @param context test context
* @param condition function which will be evaluated while waiting
+ * @param context test context
* @param timeout max wait time in milliseconds to wait on given condition
* @param errorMessage message which will be used to construct the failure
* exception in case the condition will still return {@code true}
@@ -134,6 +136,39 @@ public class TestUtil {
}
/**
+ * A simplified variant of
+ * {@link #waitWhile(Function, Object, long, Function)}.
+ * <p>
+ * Waits while given condition is {@code true} for a given amount of
+ * milliseconds.
+ * <p>
+ * Will process UI events while waiting in UI thread, if called from
+ * background thread, just waits.
+ *
+ * @param condition function which will be evaluated while waiting
+ * @param timeout max wait time in milliseconds to wait on given condition
+ * @return value of condition when method returned
+ */
+ public static boolean waitWhile(Supplier<Boolean> condition, long timeout) throws Exception {
+ if (condition == null) {
+ condition = () -> true;
+ }
+ long start = System.currentTimeMillis();
+ Display display = Display.getCurrent();
+ while (System.currentTimeMillis() - start < timeout && condition.get()) {
+ Thread.yield();
+ if (display != null && !display.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ Thread.sleep(1);
+ }
+ } else {
+ Thread.sleep(5);
+ }
+ }
+ return condition.get();
+ }
+
+ /**
* Utility for waiting until the execution of jobs of any family has
* finished or timeout is reached. If no jobs are running, the method waits
* given minimum wait time. While this method is waiting for jobs, UI events
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/InputStreamMonitorTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/InputStreamMonitorTests.java
new file mode 100644
index 000000000..b6a3b3c73
--- /dev/null
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/InputStreamMonitorTests.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Paul Pazderski and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.tests.console;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.charset.Charset;
+import java.util.Set;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.internal.core.DebugCoreMessages;
+import org.eclipse.debug.internal.core.InputStreamMonitor;
+import org.eclipse.debug.tests.AbstractDebugTest;
+import org.eclipse.debug.tests.TestUtil;
+import org.eclipse.debug.tests.TestsPlugin;
+import org.junit.Test;
+
+/**
+ * Tests the {@link InputStreamMonitor}.
+ */
+public class InputStreamMonitorTests extends AbstractDebugTest {
+
+ /**
+ * Simple test for input stream monitor. Write some bytes before starting
+ * the monitor, some after and check if they are correctly transfered.
+ */
+ @Test
+ @SuppressWarnings("resource")
+ public void testInputStreamMonitor() throws Exception {
+ PipedInputStream sysin = new PipedInputStream();
+ InputStreamMonitor monitor = new InputStreamMonitor(new PipedOutputStream(sysin));
+
+ byte[] content = new byte[100];
+ for (int i = 0; i < content.length; i++) {
+ content[i] = (byte) (i % 255);
+ }
+ try {
+ int half = content.length / 2;
+ monitor.write(content, 0, half);
+ monitor.startMonitoring();
+ monitor.write(content, half, content.length - half);
+ Thread.sleep(30);
+
+ byte[] readBack = new byte[content.length];
+ int read = sysin.read(readBack);
+ assertEquals("Monitor wrote to few bytes.", read, content.length);
+ assertEquals("Monitor wrote to much bytes.", 0, sysin.available());
+ assertArrayEquals("Monitor wrote wrong content.", content, readBack);
+ } finally {
+ monitor.close();
+ }
+ }
+
+ /**
+ * Test that passing <code>null</code> as charset does not raise exceptions.
+ */
+ @Test
+ @SuppressWarnings("resource")
+ public void testNullCharset() throws Exception {
+ PipedInputStream sysin = new PipedInputStream();
+ InputStreamMonitor monitor = new InputStreamMonitor(new PipedOutputStream(sysin), (Charset) null);
+ String text = "o\u00F6O\u00EFiI\u00D6\u00D8\u00F8";
+ try {
+ monitor.startMonitoring();
+ monitor.write(text);
+ Thread.sleep(30);
+
+ byte[] readBack = new byte[1000];
+ int len = sysin.read(readBack);
+ assertEquals("Monitor wrote wrong content.", text, new String(readBack, 0, len));
+ } finally {
+ monitor.close();
+ }
+ }
+
+ /**
+ * Test different combinations of stream closing.
+ */
+ @Test
+ @SuppressWarnings("resource")
+ public void testClose() throws Exception {
+ Set<Thread> allThreads = Thread.getAllStackTraces().keySet();
+ long alreadyLeakedThreads = allThreads.stream().filter(t -> DebugCoreMessages.InputStreamMonitor_label.equals(t.getName())).count();
+ if (alreadyLeakedThreads > 0) {
+ Platform.getLog(TestsPlugin.class).warn("Test started with " + alreadyLeakedThreads + " leaked monitor threads.");
+ }
+
+ {
+ ClosableTestOutputStream testStream = new ClosableTestOutputStream();
+ InputStreamMonitor monitor = new InputStreamMonitor(testStream);
+ assertEquals("Stream closed to early.", 0, testStream.numClosed);
+ monitor.closeInputStream();
+ TestUtil.waitWhile(() -> testStream.numClosed == 0, 100);
+ assertEquals("Stream not closed.", 1, testStream.numClosed);
+ }
+ {
+ ClosableTestOutputStream testStream = new ClosableTestOutputStream();
+ InputStreamMonitor monitor = new InputStreamMonitor(testStream);
+ monitor.startMonitoring();
+ assertEquals("Stream closed to early.", 0, testStream.numClosed);
+ monitor.close();
+ TestUtil.waitWhile(() -> testStream.numClosed == 0, 200);
+ assertEquals("Stream not closed.", 1, testStream.numClosed);
+ }
+ {
+ ClosableTestOutputStream testStream = new ClosableTestOutputStream();
+ InputStreamMonitor monitor = new InputStreamMonitor(testStream);
+ monitor.startMonitoring();
+ assertEquals("Stream closed to early.", 0, testStream.numClosed);
+ monitor.closeInputStream();
+ monitor.close();
+ monitor.close();
+ TestUtil.waitWhile(() -> testStream.numClosed == 0, 100);
+ assertEquals("Stream not closed or to often.", 1, testStream.numClosed);
+ }
+
+ allThreads = Thread.getAllStackTraces().keySet();
+ long numMonitorThreads = allThreads.stream().filter(t -> DebugCoreMessages.InputStreamMonitor_label.equals(t.getName())).count();
+ assertEquals("Leaked monitor threads.", 0, numMonitorThreads);
+ }
+
+ /**
+ * Extension of output stream to log calls to {@link #close()}.
+ */
+ public static class ClosableTestOutputStream extends OutputStream {
+ public volatile int numClosed = 0;
+
+ @Override
+ public void close() throws IOException {
+ numClosed++;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ }
+ }
+}
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/OutputStreamMonitorTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/OutputStreamMonitorTests.java
new file mode 100644
index 000000000..239aee37d
--- /dev/null
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/OutputStreamMonitorTests.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Paul Pazderski and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.tests.console;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.debug.core.IBinaryStreamListener;
+import org.eclipse.debug.core.IStreamListener;
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
+import org.eclipse.debug.core.model.IStreamMonitor;
+import org.eclipse.debug.internal.core.OutputStreamMonitor;
+import org.eclipse.debug.tests.AbstractDebugTest;
+import org.eclipse.debug.tests.TestUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the {@link OutputStreamMonitor}.
+ */
+public class OutputStreamMonitorTests extends AbstractDebugTest {
+
+ /** Stream to simulate an application writing to system out. */
+ PipedOutputStream sysout = new PipedOutputStream();
+ /** The {@link OutputStreamMonitor} used for the test runs. */
+ TestOutputStreamMonitor monitor;
+ /** The bytes received through listener. */
+ ByteArrayOutputStream notifiedBytes = new ByteArrayOutputStream();
+ /** The strings received through listener. */
+ StringBuilder notifiedChars = new StringBuilder();
+
+ IBinaryStreamListener fBinaryListener = new IBinaryStreamListener() {
+ @Override
+ public void streamAppended(byte[] data, IBinaryStreamMonitor mon) {
+ if (monitor == mon) {
+ try {
+ notifiedBytes.write(data);
+ } catch (IOException e) {
+ }
+ }
+ }
+ };
+ IStreamListener fStreamListener = new IStreamListener() {
+ @Override
+ public void streamAppended(String text, IStreamMonitor mon) {
+ if (monitor == mon) {
+ notifiedChars.append(text);
+ }
+ }
+ };
+
+ @Override
+ @Before
+ @SuppressWarnings("resource")
+ public void setUp() throws IOException {
+ monitor = new TestOutputStreamMonitor(new PipedInputStream(sysout), null);
+ }
+
+ /**
+ * Simple test for output stream monitor. Test buffering and listeners.
+ */
+ @Test
+ public void testBufferedOutputStreamMonitor() throws Exception {
+ String input = "o\u00F6O";
+ byte[] byteInput = input.getBytes(StandardCharsets.UTF_8);
+ try {
+ monitor.addBinaryListener(fBinaryListener);
+ monitor.addListener(fStreamListener);
+
+ sysout.write(byteInput, 0, 2);
+ sysout.flush();
+ monitor.startMonitoring();
+ TestUtil.waitWhile(() -> notifiedBytes.size() < 2, 1000);
+ String contents = monitor.getContents();
+ assertEquals("Monitor read wrong content.", input.substring(0, 1), contents);
+ assertEquals("Notified and buffered content differ.", contents, notifiedChars.toString());
+ assertEquals("Failed to access buffered content twice.", contents, monitor.getContents());
+ byte[] data = monitor.getData();
+ byte[] expected = new byte[2];
+ System.arraycopy(byteInput, 0, expected, 0, 2);
+ assertArrayEquals("Monitor read wrong binary content.", expected, data);
+ assertArrayEquals("Notified and buffered binary content differ.", data, notifiedBytes.toByteArray());
+ assertArrayEquals("Failed to access buffered binary content twice.", data, monitor.getData());
+
+ monitor.flushContents();
+ sysout.write(byteInput, 2, byteInput.length - 2);
+ sysout.flush();
+ TestUtil.waitWhile(() -> notifiedBytes.size() < byteInput.length, 1000);
+ contents = monitor.getContents();
+ assertEquals("Monitor buffered wrong content.", input.substring(1), contents);
+ assertEquals("Failed to access buffered content twice.", contents, monitor.getContents());
+ assertEquals("Wrong content through listener.", input, notifiedChars.toString());
+ data = monitor.getData();
+ expected = new byte[byteInput.length - 2];
+ System.arraycopy(byteInput, 2, expected, 0, expected.length);
+ assertArrayEquals("Monitor read wrong binary content.", expected, data);
+ assertArrayEquals("Failed to access buffered binary content twice.", data, monitor.getData());
+ assertArrayEquals("Wrong binary content through listener.", byteInput, notifiedBytes.toByteArray());
+ } finally {
+ sysout.close();
+ monitor.close();
+ }
+ }
+
+ /**
+ * Simple test for output stream monitor. Test listeners without buffering.
+ */
+ @Test
+ public void testUnbufferedOutputStreamMonitor() throws Exception {
+ String input = "o\u00F6O";
+ byte[] byteInput = input.getBytes(StandardCharsets.UTF_8);
+ try {
+ monitor.addBinaryListener(fBinaryListener);
+ monitor.addListener(fStreamListener);
+
+ sysout.write(byteInput, 0, 2);
+ sysout.flush();
+ monitor.setBuffered(false);
+ monitor.startMonitoring();
+ TestUtil.waitWhile(() -> notifiedBytes.size() < 2, 1000);
+ assertEquals("Monitor read wrong content.", input.substring(0, 1), notifiedChars.toString());
+ byte[] expected = new byte[2];
+ System.arraycopy(byteInput, 0, expected, 0, 2);
+ assertArrayEquals("Monitor read wrong binary content.", expected, notifiedBytes.toByteArray());
+
+ monitor.flushContents();
+ sysout.write(byteInput, 2, byteInput.length - 2);
+ sysout.flush();
+ TestUtil.waitWhile(() -> notifiedBytes.size() < byteInput.length, 1000);
+ assertEquals("Wrong content through listener.", input, notifiedChars.toString());
+ expected = new byte[byteInput.length - 2];
+ System.arraycopy(byteInput, 2, expected, 0, expected.length);
+ assertArrayEquals("Wrong binary content through listener.", byteInput, notifiedBytes.toByteArray());
+ } finally {
+ sysout.close();
+ monitor.close();
+ }
+ }
+
+ /**
+ * Test that passing <code>null</code> as charset does not raise exceptions.
+ */
+ @Test
+ public void testNullCharset() throws Exception {
+ String input = "o\u00F6O\u00EFiI\u00D6\u00D8\u00F8";
+
+ monitor.addListener(fStreamListener);
+ monitor.startMonitoring();
+ try (PrintStream out = new PrintStream(sysout)) {
+ out.print(input);
+ }
+ sysout.flush();
+
+ TestUtil.waitWhile(() -> notifiedChars.length() < input.length(), 500);
+ assertEquals("Monitor read wrong content.", input, notifiedChars.toString());
+ }
+
+ /**
+ * {@link OutputStreamMonitor} with public {@link #startMonitoring()} for
+ * testing.
+ */
+ private static class TestOutputStreamMonitor extends OutputStreamMonitor {
+
+ public TestOutputStreamMonitor(InputStream stream, Charset charset) {
+ super(stream, charset);
+ }
+
+ @Override
+ public void startMonitoring() {
+ super.startMonitoring();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ }
+ }
+}
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java
index 7b274bcc2..e6eb8b195 100644
--- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java
@@ -14,13 +14,15 @@
package org.eclipse.debug.tests.console;
import static org.junit.Assert.assertArrayEquals;
-
-import java.io.ByteArrayInputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.MessageFormat;
@@ -51,6 +53,7 @@ import org.eclipse.debug.tests.launching.LaunchConfigurationTests;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.console.ConsoleColorProvider;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
@@ -405,4 +408,121 @@ public class ProcessConsoleTests extends AbstractDebugTest {
TestUtil.waitForJobs(name.getMethodName(), 0, 1000);
}
}
+
+ /**
+ * Simulate the common case of a process which constantly produce output.
+ * This should cover the situation that a process produce output before
+ * ProcessConsole is initialized and more output after console is ready.
+ */
+ @Test
+ public void testOutput() throws Exception {
+ String[] lines = new String[] {
+ "'Native' process started.",
+ "'Eclipse' process started. Stream proxying started.",
+ "Console created.", "Console initialized.",
+ "Stopping mock process.", };
+ String consoleEncoding = StandardCharsets.UTF_8.name();
+ try (PipedOutputStream procOut = new PipedOutputStream(); PrintStream sysout = new PrintStream(procOut, true, consoleEncoding)) {
+ @SuppressWarnings("resource")
+ final MockProcess mockProcess = new MockProcess(new PipedInputStream(procOut), null, MockProcess.RUN_FOREVER);
+ sysout.println(lines[0]);
+ try {
+ Map<String, Object> launchConfigAttributes = new HashMap<>();
+ launchConfigAttributes.put(DebugPlugin.ATTR_CONSOLE_ENCODING, consoleEncoding);
+ final IProcess process = mockProcess.toRuntimeProcess("simpleOutput", launchConfigAttributes);
+ sysout.println(lines[1]);
+ @SuppressWarnings("restriction")
+ final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding);
+ sysout.println(lines[2]);
+ try {
+ console.initialize();
+ sysout.println(lines[3]);
+ sysout.println(lines[4]);
+ mockProcess.destroy();
+ sysout.close();
+ TestUtil.processUIEvents(200);
+
+ for (int i = 0; i < lines.length; i++) {
+ IRegion lineInfo = console.getDocument().getLineInformation(i);
+ String line = console.getDocument().get(lineInfo.getOffset(), lineInfo.getLength());
+ assertEquals("Wrong content in line " + i, lines[i], line);
+ }
+ } finally {
+ console.destroy();
+ }
+ } finally {
+ mockProcess.destroy();
+ }
+ }
+ }
+
+ /**
+ * Test a process which produces binary output and a launch which redirects
+ * output to file. The process output must not be changed in any way due to
+ * the redirection. See bug 558463.
+ */
+ @Test
+ public void testBinaryOutputToFile() throws Exception {
+ byte[] output = new byte[] { (byte) 0xac };
+ String consoleEncoding = StandardCharsets.UTF_8.name();
+
+ final File outFile = createTmpFile("testoutput.bin");
+ final MockProcess mockProcess = new MockProcess(new ByteArrayInputStream(output), null, MockProcess.RUN_FOREVER);
+ try {
+ Map<String, Object> launchConfigAttributes = new HashMap<>();
+ launchConfigAttributes.put(DebugPlugin.ATTR_CONSOLE_ENCODING, consoleEncoding);
+ launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath());
+ launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false);
+ final IProcess process = mockProcess.toRuntimeProcess("redirectBinaryOutput", launchConfigAttributes);
+ @SuppressWarnings("restriction")
+ final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding);
+ try {
+ console.initialize();
+ mockProcess.waitFor(100, TimeUnit.MILLISECONDS);
+ mockProcess.destroy();
+ } finally {
+ console.destroy();
+ }
+ } finally {
+ mockProcess.destroy();
+ }
+
+ byte[] receivedOutput = Files.readAllBytes(outFile.toPath());
+ assertArrayEquals(output, receivedOutput);
+ }
+
+ /**
+ * Test a process which reads binary input from a file through Eclipse
+ * console. The input must not be changed in any way due to the redirection.
+ * See bug 558463.
+ */
+ @Test
+ public void testBinaryInputFromFile() throws Exception {
+ byte[] input = new byte[] { (byte) 0xac };
+ String consoleEncoding = StandardCharsets.UTF_8.name();
+
+ final File inFile = createTmpFile("testinput.bin");
+ Files.write(inFile.toPath(), input);
+ final MockProcess mockProcess = new MockProcess(input.length, testTimeout);
+ try {
+ Map<String, Object> launchConfigAttributes = new HashMap<>();
+ launchConfigAttributes.put(DebugPlugin.ATTR_CONSOLE_ENCODING, consoleEncoding);
+ launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_STDIN_FILE, inFile.getCanonicalPath());
+ launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false);
+ final IProcess process = mockProcess.toRuntimeProcess("redirectBinaryInput", launchConfigAttributes);
+ @SuppressWarnings("restriction")
+ final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), consoleEncoding);
+ try {
+ console.initialize();
+ mockProcess.waitFor(testTimeout, TimeUnit.MILLISECONDS);
+ } finally {
+ console.destroy();
+ }
+ } finally {
+ mockProcess.destroy();
+ }
+
+ byte[] receivedInput = mockProcess.getReceivedInput();
+ assertArrayEquals(input, receivedInput);
+ }
}
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java
index a759e5dbc..472a894e0 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java
@@ -13,6 +13,7 @@
* Paul Pazderski - Bug 545769: fixed rare UTF-8 character corruption bug
* Paul Pazderski - Bug 552015: console finished signaled to late if input is connected to file
* Paul Pazderski - Bug 251642: add termination time in console label
+ * Paul Pazderski - Bug 558463: add handling of raw stream content instead of strings
*******************************************************************************/
package org.eclipse.debug.internal.ui.views.console;
@@ -53,10 +54,13 @@ import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.IBinaryStreamListener;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.IStreamListener;
+import org.eclipse.debug.core.model.IBinaryStreamMonitor;
+import org.eclipse.debug.core.model.IBinaryStreamsProxy;
import org.eclipse.debug.core.model.IFlushableStreamMonitor;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
@@ -711,11 +715,25 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
* Received output will be redirected to given {@link IOConsoleOutputStream} to
* get it shown in console and to {@link #fFileOutputStream} if set.
*/
- private class StreamListener implements IStreamListener {
+ private class StreamListener implements IStreamListener, IBinaryStreamListener {
private IOConsoleOutputStream fStream;
+ /**
+ * The monitors from which this listener class is notified about new content.
+ * Initial and for a long time IO handling in context of Eclipse console was
+ * based on strings and later extended with a variant passing the raw binary
+ * data.
+ * <p>
+ * As a result of this history it is expectable to have a stream monitor passing
+ * the content decoded as string but optional to have access to the
+ * raw/unchanged data.
+ * <p>
+ * Therefore the following two monitor instances either point to the same class
+ * implementing both interfaces or the binary variant is <code>null</code>.
+ */
private IStreamMonitor fStreamMonitor;
+ private IBinaryStreamMonitor fBinaryStreamMonitor;
private String fStreamId;
@@ -723,12 +741,16 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
private boolean fStreamClosed = false;
public StreamListener(String streamIdentifier, IStreamMonitor monitor, IOConsoleOutputStream stream) {
- this.fStreamId = streamIdentifier;
- this.fStreamMonitor = monitor;
- this.fStream = stream;
+ fStreamId = streamIdentifier;
+ fStreamMonitor = monitor;
+ fStream = stream;
fStreamMonitor.addListener(this);
- //fix to bug 121454. Ensure that output to fast processes is processed.
- flushAndDisableBuffer(monitor);
+ if (fStreamMonitor instanceof IBinaryStreamMonitor && fFileOutputStream != null) {
+ fBinaryStreamMonitor = (IBinaryStreamMonitor) monitor;
+ fBinaryStreamMonitor.addBinaryListener(this);
+ }
+ // fix to bug 121454. Ensure that output to fast processes is processed.
+ flushAndDisableBuffer();
}
/**
@@ -737,17 +759,37 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
*
* @param monitor the monitor which might have buffered content
*/
- private void flushAndDisableBuffer(IStreamMonitor monitor) {
+ private void flushAndDisableBuffer() {
+ byte[] data = null;
String contents;
- synchronized (monitor) {
- contents = monitor.getContents();
- if (monitor instanceof IFlushableStreamMonitor) {
- IFlushableStreamMonitor m = (IFlushableStreamMonitor) monitor;
+ synchronized (fStreamMonitor) {
+ if (fBinaryStreamMonitor != null) {
+ data = fBinaryStreamMonitor.getData();
+ }
+ contents = fStreamMonitor.getContents();
+ if (fStreamMonitor instanceof IFlushableStreamMonitor) {
+ IFlushableStreamMonitor m = (IFlushableStreamMonitor) fStreamMonitor;
m.flushContents();
m.setBuffered(false);
}
}
- streamAppended(contents, monitor);
+ if (data != null) {
+ streamAppended(data, fBinaryStreamMonitor);
+ }
+ streamAppended(contents, fStreamMonitor);
+ }
+
+ @Override
+ public void streamAppended(byte[] data, IBinaryStreamMonitor monitor) {
+ if (fFileOutputStream != null) {
+ synchronized (fFileOutputStream) {
+ try {
+ fFileOutputStream.write(data);
+ } catch (IOException e) {
+ DebugUIPlugin.log(e);
+ }
+ }
+ }
}
@Override
@@ -755,22 +797,20 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
if (text == null || text.length() == 0) {
return;
}
- try {
- if (fStream != null) {
+ if (fStream != null) {
+ try {
fStream.write(text);
+ } catch (IOException e) {
+ DebugUIPlugin.log(e);
}
- if (fFileOutputStream != null) {
- Charset charset = getCharset();
- synchronized (fFileOutputStream) {
- if (charset == null) {
- fFileOutputStream.write(text.getBytes());
- } else {
- fFileOutputStream.write(text.getBytes(charset));
- }
- }
- }
- } catch (IOException e) {
- DebugUIPlugin.log(e);
+ }
+ // If the monitor does not provide the raw data API and we need to redirect to
+ // a file the second best (and in the past only) option is to write the encoded
+ // text to file.
+ if (fBinaryStreamMonitor == null && fFileOutputStream != null) {
+ Charset charset = getCharset();
+ byte[] data = charset == null ? text.getBytes() : text.getBytes(charset);
+ streamAppended(data, null);
}
}
@@ -780,6 +820,9 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
}
synchronized (fStreamMonitor) {
fStreamMonitor.removeListener(this);
+ if (fBinaryStreamMonitor != null) {
+ fBinaryStreamMonitor.removeBinaryListener(this);
+ }
fStreamClosed = true;
try {
@@ -842,31 +885,56 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe
if (fInput == null || fStreamsClosed) {
return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
}
- Charset encoding = getCharset();
- readingStream = fInput;
- InputStreamReader streamReader = (encoding == null ? new InputStreamReader(readingStream)
- : new InputStreamReader(readingStream, encoding));
- try {
- char[] cbuf = new char[1024];
- int charRead = 0;
- while (charRead >= 0 && !monitor.isCanceled()) {
- if (fInput == null || fStreamsClosed) {
- break;
- }
- if (fInput != readingStream) {
- readingStream = fInput;
- streamReader = (encoding == null ? new InputStreamReader(readingStream)
- : new InputStreamReader(readingStream, encoding));
+ if (streamsProxy instanceof IBinaryStreamsProxy) {
+ // Pass data without processing. The preferred variant. There is no need for
+ // this job to know about encodings.
+ try {
+ byte[] buffer = new byte[1024];
+ int bytesRead = 0;
+ while (bytesRead >= 0 && !monitor.isCanceled()) {
+ if (fInput == null || fStreamsClosed) {
+ break;
+ }
+ if (fInput != readingStream) {
+ readingStream = fInput;
+ }
+ bytesRead = readingStream.read(buffer);
+ if (bytesRead > 0) {
+ ((IBinaryStreamsProxy) streamsProxy).write(buffer, 0, bytesRead);
+ }
}
+ } catch (IOException e) {
+ DebugUIPlugin.log(e);
+ }
+ } else {
+ // Decode data to strings. The legacy variant used if the proxy does not
+ // implement the binary API.
+ Charset encoding = getCharset();
+ readingStream = fInput;
+ InputStreamReader streamReader = (encoding == null ? new InputStreamReader(readingStream)
+ : new InputStreamReader(readingStream, encoding));
+ try {
+ char[] cbuf = new char[1024];
+ int charRead = 0;
+ while (charRead >= 0 && !monitor.isCanceled()) {
+ if (fInput == null || fStreamsClosed) {
+ break;
+ }
+ if (fInput != readingStream) {
+ readingStream = fInput;
+ streamReader = (encoding == null ? new InputStreamReader(readingStream)
+ : new InputStreamReader(readingStream, encoding));
+ }
- charRead = streamReader.read(cbuf);
- if (charRead > 0) {
- String s = new String(cbuf, 0, charRead);
- streamsProxy.write(s);
+ charRead = streamReader.read(cbuf);
+ if (charRead > 0) {
+ String s = new String(cbuf, 0, charRead);
+ streamsProxy.write(s);
+ }
}
+ } catch (IOException e) {
+ DebugUIPlugin.log(e);
}
- } catch (IOException e) {
- DebugUIPlugin.log(e);
}
readingStream = null;
return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
diff --git a/org.eclipse.ui.console/META-INF/MANIFEST.MF b/org.eclipse.ui.console/META-INF/MANIFEST.MF
index 9c30f3dfb..9c3bc07da 100644
--- a/org.eclipse.ui.console/META-INF/MANIFEST.MF
+++ b/org.eclipse.ui.console/META-INF/MANIFEST.MF
@@ -14,7 +14,8 @@ Require-Bundle: org.eclipse.ui;bundle-version="[3.5.0,4.0.0)",
org.eclipse.ui.workbench.texteditor;bundle-version="[3.5.0,4.0.0)",
org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
org.eclipse.core.expressions;bundle-version="[3.4.0,4.0.0)",
- org.eclipse.core.variables;bundle-version="[3.2.800,4.0.0)"
+ org.eclipse.core.variables;bundle-version="[3.2.800,4.0.0)",
+ org.eclipse.debug.core;bundle-version="[3.16.0,4.0.0)"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Automatic-Module-Name: org.eclipse.ui.console
diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleInputStream.java b/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleInputStream.java
index d4442f41e..56464eba5 100644
--- a/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleInputStream.java
+++ b/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleInputStream.java
@@ -16,7 +16,7 @@ package org.eclipse.ui.console;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
@@ -150,14 +150,10 @@ public class IOConsoleInputStream extends InputStream {
* @param text the text to append to the buffer.
*/
public synchronized void appendData(String text) {
- String encoding = console.getEncoding();
+ Charset charset = console.getCharset();
byte[] newData;
- if (encoding!=null) {
- try {
- newData = text.getBytes(encoding);
- } catch (UnsupportedEncodingException e) {
- newData = text.getBytes();
- }
+ if (charset != null) {
+ newData = text.getBytes(charset);
} else {
newData = text.getBytes();
}
diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleOutputStream.java b/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleOutputStream.java
index 2faaa3692..bbe638bb5 100644
--- a/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleOutputStream.java
+++ b/org.eclipse.ui.console/src/org/eclipse/ui/console/IOConsoleOutputStream.java
@@ -18,10 +18,10 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
+import org.eclipse.debug.internal.core.StreamDecoder;
import org.eclipse.swt.graphics.Color;
import org.eclipse.ui.WorkbenchEncoding;
import org.eclipse.ui.internal.console.IOConsolePartitioner;
-import org.eclipse.ui.internal.console.StreamDecoder;
/**
* OutputStream used to write to an IOConsole.
diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/StreamDecoder.java b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/StreamDecoder.java
index 0bc54f2af..e334aa22e 100644
--- a/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/StreamDecoder.java
+++ b/org.eclipse.ui.console/src/org/eclipse/ui/internal/console/StreamDecoder.java
@@ -14,93 +14,17 @@
package org.eclipse.ui.internal.console;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
/**
- * @since 3.7
+ * @deprecated class was moved to
+ * {@link org.eclipse.debug.internal.core.StreamDecoder}
*/
-public class StreamDecoder {
-
- static private final int BUFFER_SIZE = 4096;
-
- private final CharsetDecoder decoder;
- private final ByteBuffer inputBuffer;
- private final CharBuffer outputBuffer;
- private boolean finished;
+@Deprecated
+public class StreamDecoder extends org.eclipse.debug.internal.core.StreamDecoder {
public StreamDecoder(Charset charset) {
- this.decoder = charset.newDecoder();
- this.decoder.onMalformedInput(CodingErrorAction.REPLACE);
- this.decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
- this.inputBuffer = ByteBuffer.allocate(StreamDecoder.BUFFER_SIZE);
- this.inputBuffer.flip();
- this.outputBuffer = CharBuffer.allocate(StreamDecoder.BUFFER_SIZE);
- this.finished = false;
- }
-
- private void consume(StringBuilder consumer) {
- this.outputBuffer.flip();
- consumer.append(this.outputBuffer);
- this.outputBuffer.clear();
- }
-
- private void internalDecode(StringBuilder consumer, byte[] buffer, int offset, int length) {
- assert (offset >= 0);
- assert (length >= 0);
- int position = offset;
- int end = offset + length;
- assert (end <= buffer.length);
- boolean finishedReading = false;
- do {
- CoderResult result = this.decoder.decode(this.inputBuffer, this.outputBuffer, false);
- if (result.isOverflow()) {
- this.consume(consumer);
- } else if (result.isUnderflow()) {
- this.inputBuffer.compact();
- int remaining = this.inputBuffer.remaining();
- assert (remaining > 0);
- int read = Math.min(remaining, end - position);
- if (read > 0) {
- this.inputBuffer.put(buffer, position, read);
- position += read;
- } else {
- finishedReading = true;
- }
- this.inputBuffer.flip();
- } else {
- assert false;
- }
- } while (!finishedReading);
+ super(charset);
}
-
- public void decode(StringBuilder consumer, byte[] buffer, int offset, int length) {
- this.internalDecode(consumer, buffer, offset, length);
- this.consume(consumer);
- }
-
- public void finish(StringBuilder consumer) {
- if (this.finished) {
- return;
- }
- this.finished = true;
- CoderResult result;
- result = this.decoder.decode(this.inputBuffer, this.outputBuffer, true);
- assert (result.isOverflow() || result.isUnderflow());
- do {
- result = this.decoder.flush(this.outputBuffer);
- if (result.isOverflow()) {
- this.consume(consumer);
- } else {
- assert result.isUnderflow();
- }
- } while (!result.isUnderflow());
- this.consume(consumer);
- }
-
}

Back to the top