Skip to content

Commit 28f717b

Browse files
committed
Merge pull request #152 from scijava/stdout-stderr
Let the ConsoleService track stdout and stderr messages
2 parents aec0a30 + b672ea8 commit 28f717b

File tree

13 files changed

+866
-26
lines changed

13 files changed

+866
-26
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
/**
3939
* Abstract superclass of {@link ConsoleArgument} implementations.
40-
*
40+
*
4141
* @author Curtis Rueden
4242
*/
4343
public abstract class AbstractConsoleArgument extends

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
* is encouraged to instead extend {@link AbstractConsoleArgument}, for
4747
* convenience.
4848
* </p>
49-
*
49+
*
5050
* @author Curtis Rueden
5151
*/
5252
public interface ConsoleArgument extends HandlerPlugin<LinkedList<String>> {

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
* Interface for service that manages interaction with the console.
4141
* <p>
4242
* In particular, this is the service that defines how command line arguments
43-
* are handled.
43+
* are handled. It also provides an extension mechanism for {@code stdout} and
44+
* {@code stderr} logging.
4445
* </p>
45-
*
46+
*
4647
* @author Curtis Rueden
4748
*/
4849
public interface ConsoleService extends
@@ -55,4 +56,13 @@ public interface ConsoleService extends
5556
*/
5657
void processArgs(String... args);
5758

59+
/** Adds a listener for output sent to {@code stdout} or {@code stderr}. */
60+
void addOutputListener(OutputListener l);
61+
62+
/** Removes a listener for output sent to {@code stdout} or {@code stderr}. */
63+
void removeOutputListener(OutputListener l);
64+
65+
/** Notifies listeners of output sent to {@code stdout} or {@code stderr}. */
66+
void notifyListeners(OutputEvent event);
67+
5868
}

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

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,24 @@
3131

3232
package org.scijava.console;
3333

34+
import java.io.OutputStream;
35+
import java.io.PrintStream;
36+
import java.util.ArrayList;
3437
import java.util.LinkedList;
3538

39+
import org.scijava.Context;
40+
import org.scijava.console.OutputEvent.Source;
3641
import org.scijava.log.LogService;
3742
import org.scijava.plugin.AbstractHandlerService;
3843
import org.scijava.plugin.Parameter;
3944
import org.scijava.plugin.Plugin;
4045
import org.scijava.service.Service;
46+
import org.scijava.thread.ThreadService;
47+
import org.scijava.thread.ThreadService.ThreadContext;
4148

4249
/**
4350
* Default service for managing interaction with the console.
44-
*
51+
*
4552
* @author Curtis Rueden
4653
*/
4754
@Plugin(type = Service.class)
@@ -50,9 +57,20 @@ public class DefaultConsoleService extends
5057
ConsoleService
5158
{
5259

60+
@Parameter
61+
private ThreadService threadService;
62+
5363
@Parameter
5464
private LogService log;
5565

66+
private MultiPrintStream sysout, syserr;
67+
private OutputStreamReporter out, err;
68+
69+
/** List of listeners for {@code stdout} and {@code stderr} output. */
70+
private ArrayList<OutputListener> listeners;
71+
72+
private OutputListener[] cachedListeners;
73+
5674
// -- ConsoleService methods --
5775

5876
@Override
@@ -76,6 +94,32 @@ public void processArgs(final String... args) {
7694
}
7795
}
7896

97+
@Override
98+
public void addOutputListener(final OutputListener l) {
99+
if (listeners == null) initListeners();
100+
synchronized (listeners) {
101+
listeners.add(l);
102+
cacheListeners();
103+
}
104+
}
105+
106+
@Override
107+
public void removeOutputListener(final OutputListener l) {
108+
if (listeners == null) initListeners();
109+
synchronized (listeners) {
110+
listeners.remove(l);
111+
cacheListeners();
112+
}
113+
}
114+
115+
@Override
116+
public void notifyListeners(final OutputEvent event) {
117+
if (listeners == null) initListeners();
118+
final OutputListener[] toNotify = cachedListeners;
119+
for (final OutputListener l : toNotify)
120+
l.outputOccurred(event);
121+
}
122+
79123
// -- PTService methods --
80124

81125
@Override
@@ -91,4 +135,89 @@ public Class<LinkedList<String>> getType() {
91135
return (Class) LinkedList.class;
92136
}
93137

138+
// -- Disposable methods --
139+
140+
@Override
141+
public void dispose() {
142+
if (out != null) sysout.getParent().removeOutputStream(out);
143+
if (err != null) syserr.getParent().removeOutputStream(err);
144+
}
145+
146+
// -- Helper methods - lazy initialization --
147+
148+
/** Initializes {@link #listeners} and related data structures. */
149+
private synchronized void initListeners() {
150+
if (listeners != null) return; // already initialized
151+
152+
sysout = multiPrintStream(System.out);
153+
if (System.out != sysout) System.setOut(sysout);
154+
out = new OutputStreamReporter(Source.STDOUT);
155+
sysout.getParent().addOutputStream(out);
156+
157+
syserr = multiPrintStream(System.err);
158+
if (System.err != syserr) System.setErr(syserr);
159+
err = new OutputStreamReporter(Source.STDERR);
160+
syserr.getParent().addOutputStream(err);
161+
162+
listeners = new ArrayList<OutputListener>();
163+
cachedListeners = listeners.toArray(new OutputListener[0]);
164+
}
165+
166+
// -- Helper methods --
167+
168+
private void cacheListeners() {
169+
cachedListeners = listeners.toArray(new OutputListener[listeners.size()]);
170+
}
171+
172+
private MultiPrintStream multiPrintStream(final PrintStream ps) {
173+
if (ps instanceof MultiPrintStream) return (MultiPrintStream) ps;
174+
return new MultiPrintStream(ps);
175+
}
176+
177+
// -- Helper classes --
178+
179+
/**
180+
* An output stream that publishes its output to the {@link OutputListener}s
181+
* of its associated {@link ConsoleService}.
182+
*/
183+
private class OutputStreamReporter extends OutputStream {
184+
185+
/** Source of the output stream; i.e., {@code stdout} or {@code stderr}. */
186+
private final Source source;
187+
188+
public OutputStreamReporter(final Source source) {
189+
this.source = source;
190+
}
191+
192+
// -- OutputStream methods --
193+
194+
@Override
195+
public void write(final int b) {
196+
final ThreadContext relevance = getRelevance();
197+
if (relevance == ThreadContext.OTHER) return; // different context
198+
publish(relevance, "" + b);
199+
}
200+
201+
@Override
202+
public void write(final byte[] buf, final int off, final int len) {
203+
final ThreadContext relevance = getRelevance();
204+
if (relevance == ThreadContext.OTHER) return; // different context
205+
publish(relevance, new String(buf, off, len));
206+
}
207+
208+
// -- Helper methods --
209+
210+
private ThreadContext getRelevance() {
211+
return threadService.getThreadContext(Thread.currentThread());
212+
}
213+
214+
private void publish(final ThreadContext relevance, final String output) {
215+
final Context context = getContext();
216+
final boolean contextual = relevance == ThreadContext.SAME;
217+
final OutputEvent event =
218+
new OutputEvent(context, source, output, contextual);
219+
notifyListeners(event);
220+
}
221+
}
222+
94223
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2015 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.console;
33+
34+
import java.io.IOException;
35+
import java.io.OutputStream;
36+
import java.util.ArrayList;
37+
38+
/**
39+
* A {@code MultiOutputStream} is a collection of constituent
40+
* {@link OutputStream} objects, to which all output is forwarded.
41+
* <p>
42+
* Thanks to Ian F. Darwin for <a href=
43+
* "http://www.java2s.com/Code/Java/File-Input-Output/TeePrintStreamteesallPrintStreamoperationsintoafileratherliketheUNIXtee1command.htm"
44+
* >his implementation of a similar concept</a>.
45+
* </p>
46+
*
47+
* @author Curtis Rueden
48+
*/
49+
public class MultiOutputStream extends OutputStream {
50+
51+
private final ArrayList<OutputStream> streams;
52+
53+
private OutputStream[] cachedStreams;
54+
55+
/**
56+
* Forwards output to a list of output streams.
57+
*
58+
* @param os Output streams which will receive this stream's output.
59+
*/
60+
public MultiOutputStream(final OutputStream... os) {
61+
streams = new ArrayList<OutputStream>(os.length);
62+
for (int i = 0; i < os.length; i++) {
63+
streams.add(os[i]);
64+
}
65+
cacheStreams();
66+
}
67+
68+
// -- MultiOutputStream methods --
69+
70+
/** Adds an output stream to those receiving this stream's output. */
71+
public void addOutputStream(final OutputStream os) {
72+
synchronized (streams) {
73+
streams.add(os);
74+
cacheStreams();
75+
}
76+
}
77+
78+
/** Removes an output stream from those receiving this stream's output. */
79+
public void removeOutputStream(final OutputStream os) {
80+
synchronized (streams) {
81+
streams.remove(os);
82+
cacheStreams();
83+
}
84+
}
85+
86+
// -- OutputStream methods --
87+
88+
@Override
89+
public void write(final int b) throws IOException {
90+
final OutputStream[] toWrite = cachedStreams;
91+
for (final OutputStream stream : toWrite)
92+
stream.write(b);
93+
}
94+
95+
@Override
96+
public void write(final byte[] buf, final int off, final int len)
97+
throws IOException
98+
{
99+
final OutputStream[] toWrite = cachedStreams;
100+
for (final OutputStream stream : toWrite)
101+
stream.write(buf, off, len);
102+
}
103+
104+
// -- Closeable methods --
105+
106+
@Override
107+
public void close() throws IOException {
108+
final OutputStream[] toClose = cachedStreams;
109+
for (final OutputStream stream : toClose)
110+
stream.close();
111+
}
112+
113+
// -- Flushable methods --
114+
115+
@Override
116+
public void flush() throws IOException {
117+
final OutputStream[] toFlush = cachedStreams;
118+
for (final OutputStream stream : toFlush)
119+
stream.flush();
120+
}
121+
122+
// -- Helper methods --
123+
124+
private void cacheStreams() {
125+
cachedStreams = streams.toArray(new OutputStream[streams.size()]);
126+
}
127+
128+
}

0 commit comments

Comments
 (0)