Skip to content

Commit dd418a9

Browse files
committed
ConsoleService: initialize MultiplePrintStream correctly, add bypass
The initialization of MultiplePrintStream in DefaultConsoleService was not synchronized correctly. The global resources System.out and System.err where modified like this: read, modify, write. This can fail if two DefaultConsoleServices (in two scijava contexts) are initialized in parallel. The problem is solved in ListenableSystemStreams by using singletons.
1 parent b5ab68a commit dd418a9

4 files changed

Lines changed: 164 additions & 16 deletions

File tree

src/main/java/org/scijava/console/DefaultConsoleService.java

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
package org.scijava.console;
3434

3535
import java.io.OutputStream;
36-
import java.io.PrintStream;
3736
import java.util.ArrayList;
3837
import java.util.LinkedList;
3938
import java.util.List;
@@ -66,7 +65,6 @@ public class DefaultConsoleService extends
6665
@Parameter
6766
private LogService log;
6867

69-
private MultiPrintStream sysout, syserr;
7068
private OutputStreamReporter out, err;
7169

7270
/** List of listeners for {@code stdout} and {@code stderr} output. */
@@ -135,8 +133,8 @@ public void notifyListeners(final OutputEvent event) {
135133

136134
@Override
137135
public void dispose() {
138-
if (out != null) sysout.getParent().removeOutputStream(out);
139-
if (err != null) syserr.getParent().removeOutputStream(err);
136+
if(out != null) ListenableSystemStreams.out().removeOutputStream(out);
137+
if(err != null) ListenableSystemStreams.err().removeOutputStream(err);
140138
}
141139

142140
// -- Helper methods - lazy initialization --
@@ -145,26 +143,16 @@ public void dispose() {
145143
private synchronized void initListeners() {
146144
if (listeners != null) return; // already initialized
147145

148-
sysout = multiPrintStream(System.out);
149-
if (System.out != sysout) System.setOut(sysout);
150146
out = new OutputStreamReporter(Source.STDOUT);
151-
sysout.getParent().addOutputStream(out);
152-
153-
syserr = multiPrintStream(System.err);
154-
if (System.err != syserr) System.setErr(syserr);
147+
ListenableSystemStreams.out().addOutputStream(out);
155148
err = new OutputStreamReporter(Source.STDERR);
156-
syserr.getParent().addOutputStream(err);
149+
ListenableSystemStreams.err().addOutputStream(err);
157150

158151
listeners = new CopyOnWriteArrayList<>();
159152
}
160153

161154
// -- Helper methods --
162155

163-
private MultiPrintStream multiPrintStream(final PrintStream ps) {
164-
if (ps instanceof MultiPrintStream) return (MultiPrintStream) ps;
165-
return new MultiPrintStream(ps);
166-
}
167-
168156
/**
169157
* Gets whether two lists have exactly the same elements in them.
170158
* <p>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.console;
34+
35+
import java.io.PrintStream;
36+
import java.util.function.Consumer;
37+
38+
/**
39+
* ListenableSystemStream allows listing to System.out and System.err.
40+
*
41+
* @author Matthais Arzt
42+
*/
43+
public final class ListenableSystemStreams {
44+
45+
private ListenableSystemStreams() {
46+
// prevent from being initialized
47+
}
48+
49+
public static MultiPrintStream out() {
50+
return LazyHolder.OUT;
51+
}
52+
53+
public static MultiPrintStream err() {
54+
return LazyHolder.ERR;
55+
}
56+
57+
private static class LazyHolder { // using idiom for lazy-loaded singleton
58+
59+
private static final MultiPrintStream OUT = initStream(System.out,
60+
System::setOut);
61+
62+
private static final MultiPrintStream ERR = initStream(System.err,
63+
System::setErr);
64+
65+
private static MultiPrintStream initStream(PrintStream out,
66+
Consumer<PrintStream> streamSetter)
67+
{
68+
MultiPrintStream listenableStream = new MultiPrintStream(out);
69+
streamSetter.accept(listenableStream);
70+
return listenableStream;
71+
}
72+
}
73+
}

src/main/java/org/scijava/console/MultiPrintStream.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@
4242
*/
4343
public class MultiPrintStream extends PrintStream {
4444

45+
private final PrintStream bypass;
46+
4547
public MultiPrintStream(final OutputStream os) {
4648
super(multi(os));
49+
// NB: bypass would not work, if os is an instance of MultiOutputStream.
50+
// Therefor only provide bypass functionality, if os is an instance of PrintStream.
51+
this.bypass = (os instanceof PrintStream) ? (PrintStream) os : null;
4752
}
4853

4954
// -- MultiPrintStream methods --
@@ -52,6 +57,18 @@ public MultiOutputStream getParent() {
5257
return (MultiOutputStream) out;
5358
}
5459

60+
public void addOutputStream(OutputStream os) {
61+
getParent().addOutputStream(os);
62+
}
63+
64+
public void removeOutputStream(OutputStream os) {
65+
getParent().removeOutputStream(os);
66+
}
67+
68+
public PrintStream bypass() {
69+
return bypass;
70+
}
71+
5572
// -- Helper methods --
5673

5774
private static OutputStream multi(final OutputStream os) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
package org.scijava.console;
3+
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import java.io.ByteArrayOutputStream;
8+
import java.io.OutputStream;
9+
import java.io.PrintStream;
10+
11+
import org.junit.Test;
12+
13+
/**
14+
* Tests {@link ListenableSystemStreams}
15+
*
16+
* @author Matthias Arzt
17+
*/
18+
public class ListenableSystemStreamsTest {
19+
20+
private final String TEXT = "Hello World!\n";
21+
22+
private static PrintStream bufferingPrintStream() {
23+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
24+
return new PrintStream(buffer) {
25+
26+
@Override
27+
public String toString() {
28+
return buffer.toString();
29+
}
30+
};
31+
}
32+
33+
@Test
34+
public void testListenerSystemOut() {
35+
OutputStream listener = new ByteArrayOutputStream();
36+
ListenableSystemStreams.out().addOutputStream(listener);
37+
System.out.print(TEXT);
38+
ListenableSystemStreams.out().removeOutputStream(listener);
39+
System.out.print(TEXT);
40+
assertEquals(TEXT, listener.toString());
41+
}
42+
43+
@Test
44+
public void testListening() {
45+
// setup
46+
PrintStream output = bufferingPrintStream();
47+
OutputStream listener = new ByteArrayOutputStream();
48+
MultiPrintStream multiPrintStream = new MultiPrintStream(output);
49+
// process
50+
multiPrintStream.addOutputStream(listener);
51+
multiPrintStream.print(TEXT);
52+
// test
53+
assertEquals(TEXT, listener.toString());
54+
assertEquals(TEXT, output.toString());
55+
}
56+
57+
@Test
58+
public void testBypass() {
59+
// setup
60+
PrintStream output = bufferingPrintStream();
61+
OutputStream listener = new ByteArrayOutputStream();
62+
MultiPrintStream multiPrintStream = new MultiPrintStream(output);
63+
multiPrintStream.addOutputStream(listener);
64+
// process
65+
multiPrintStream.bypass().print(TEXT);
66+
// test
67+
assertTrue(listener.toString().isEmpty());
68+
assertEquals(TEXT, output.toString());
69+
}
70+
}

0 commit comments

Comments
 (0)