3131
3232package org .scijava .console ;
3333
34+ import java .io .OutputStream ;
35+ import java .io .PrintStream ;
36+ import java .util .ArrayList ;
3437import java .util .LinkedList ;
3538
39+ import org .scijava .Context ;
40+ import org .scijava .console .OutputEvent .Source ;
3641import org .scijava .log .LogService ;
3742import org .scijava .plugin .AbstractHandlerService ;
3843import org .scijava .plugin .Parameter ;
3944import org .scijava .plugin .Plugin ;
4045import 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}
0 commit comments