Here is my full solution which is based on the same API as @Ernest Friedman-Hill mentioned.
All may be done with single line:
String capturedOut = ISystemOutAcquire.acquire(MyClass::myStaticFunOrRunnable);
- Below solution allows passing any
Runnable
and acquire the System.out
during the Runnable.run()
method call. Of course it doesn't guarantee that another thread will not write additional data at the same time.
- The original
System.out
is restored when ISystemOutAcquire.close()
is called - the RAII-like behavior.
Unit test:
package pl.mjaron.etudes;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ISystemOutAcquireTest {
static void printingCode() {
System.out.print("Sample output.");
}
@Test
void acquireCall() {
final String contentFromCall = ISystemOutAcquire.acquire(ISystemOutAcquireTest::printingCode);
assertEquals("Sample output.", contentFromCall);
}
@Test
void acquireScope() {
final String contentFromScope;
try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
printingCode();
contentFromScope = acquire.toString();
}
assertEquals("Sample output.", contentFromScope);
}
}
The implementation:
package pl.mjaron.etudes;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public interface ISystemOutAcquire extends AutoCloseable {
PrintStream getOriginalOut();
PrintStream getNewOut();
/**
* Restores the original stream.
*/
@Override
void close();
/**
* Replaces the original {@link PrintStream} with another one.
*/
class ToPrintStream implements ISystemOutAcquire {
private final PrintStream originalOut;
private final PrintStream newOut;
public ToPrintStream(PrintStream newOut) {
this.originalOut = System.out;
this.newOut = newOut;
originalOut.flush();
newOut.flush();
System.setOut(newOut);
}
@Override
public void close() {
originalOut.flush();
newOut.flush();
System.setOut(originalOut);
}
@Override
public PrintStream getOriginalOut() {
return originalOut;
}
@Override
public PrintStream getNewOut() {
return newOut;
}
}
public static ToPrintStream acquire(final PrintStream newOut) {
return new ToPrintStream(newOut);
}
/**
* Captures the {@link System#out} to {@link ByteArrayOutputStream}.
* <p>
* Overrides the {@link #toString()} method, so all captured text is accessible.
*/
class ToByteArray extends ToPrintStream {
private final ByteArrayOutputStream byteArrayOutputStream;
public ToByteArray(final ByteArrayOutputStream byteArrayOutputStream) {
super(new PrintStream(byteArrayOutputStream));
this.byteArrayOutputStream = byteArrayOutputStream;
}
@Override
public String toString() {
return byteArrayOutputStream.toString();
}
public ByteArrayOutputStream getByteArrayOutputStream() {
return byteArrayOutputStream;
}
}
static ToByteArray acquire(final ByteArrayOutputStream byteArrayOutputStream) {
return new ToByteArray(byteArrayOutputStream);
}
static ToByteArray acquire() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
return acquire(byteArrayOutputStream);
}
static String acquire(final Runnable runnable) {
try (ISystemOutAcquire acquire = ISystemOutAcquire.acquire()) {
runnable.run();
return acquire.toString();
}
}
}