Skip to content

Commit 6d3e423

Browse files
committed
Merge branch 'console-threading'
This branch updates the Swing output console so that updates happen only on the AWT Event Dispatch Thread (EDT). See PR #10.
2 parents 5382115 + 7f44cf5 commit 6d3e423

File tree

3 files changed

+199
-36
lines changed

3 files changed

+199
-36
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@
104104
<artifactId>jdatepicker</artifactId>
105105
<version>1.3.2</version>
106106
</dependency>
107+
108+
<!-- Test scope dependencies -->
109+
<dependency>
110+
<groupId>junit</groupId>
111+
<artifactId>junit</artifactId>
112+
</dependency>
107113
</dependencies>
108114

109115
<build>

src/main/java/org/scijava/ui/swing/console/SwingConsolePane.java

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import org.scijava.console.OutputEvent.Source;
5252
import org.scijava.plugin.Parameter;
5353
import org.scijava.thread.ThreadService;
54-
import org.scijava.ui.UIService;
5554
import org.scijava.ui.console.AbstractConsolePane;
5655
import org.scijava.ui.console.ConsolePane;
5756
import org.scijava.ui.swing.StaticSwingUtils;
@@ -93,31 +92,46 @@ public void setWindow(final Component window) {
9392
this.window = window;
9493
}
9594

95+
public JTextPane getTextPane() {
96+
if (consolePanel == null) initConsolePanel();
97+
return textPane;
98+
}
99+
100+
public JScrollPane getScrollPane() {
101+
if (consolePanel == null) initConsolePanel();
102+
return scrollPane;
103+
}
104+
96105
// -- ConsolePane methods --
97106

98107
@Override
99108
public void append(final OutputEvent event) {
100109
if (consolePanel == null) initConsolePanel();
101-
final boolean atBottom = StaticSwingUtils.isScrolledToBottom(scrollPane);
102-
try {
103-
doc.insertString(doc.getLength(), event.getOutput(), getStyle(event));
104-
}
105-
catch (final BadLocationException exc) {
106-
throw new RuntimeException(exc);
107-
}
108-
if (atBottom) StaticSwingUtils.scrollToBottom(scrollPane);
110+
threadService.queue(new Runnable() {
111+
112+
@Override
113+
public void run() {
114+
final boolean atBottom =
115+
StaticSwingUtils.isScrolledToBottom(scrollPane);
116+
try {
117+
doc.insertString(doc.getLength(), event.getOutput(), getStyle(event));
118+
}
119+
catch (final BadLocationException exc) {
120+
throw new RuntimeException(exc);
121+
}
122+
if (atBottom) StaticSwingUtils.scrollToBottom(scrollPane);
123+
}
124+
});
109125
}
110126

111127
@Override
112128
public void show() {
113-
if (window == null) return;
129+
if (window == null || window.isVisible()) return;
114130
threadService.queue(new Runnable() {
115131

116132
@Override
117133
public void run() {
118-
if (!window.isVisible()) {
119-
window.setVisible(true);
120-
}
134+
window.setVisible(true);
121135
}
122136
});
123137
}
@@ -189,27 +203,4 @@ private Style getStyle(final OutputEvent event) {
189203
return contextual ? stdoutLocal : stdoutGlobal;
190204
}
191205

192-
// -- Main method --
193-
194-
/** A manual test drive of the Swing UI's console pane. */
195-
public static void main(final String[] args) throws Exception {
196-
final Context context = new Context();
197-
context.service(UIService.class).showUI();
198-
199-
System.out.println("Hello!");
200-
201-
final ThreadService threadService = context.service(ThreadService.class);
202-
threadService.run(new Runnable() {
203-
204-
@Override
205-
public void run() {
206-
System.out.println("This is a test of the emergency console system.");
207-
System.err.println("In a real emergency, your computer would explode.");
208-
}
209-
210-
}).get();
211-
212-
System.err.println("Goodbye!");
213-
}
214-
215206
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* #%L
3+
* SciJava UI components for Java Swing.
4+
* %%
5+
* Copyright (C) 2010 - 2015 Board of Regents of the University of
6+
* Wisconsin-Madison.
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
31+
package org.scijava.ui.swing.console;
32+
33+
import static org.junit.Assert.assertEquals;
34+
import static org.junit.Assert.assertTrue;
35+
36+
import java.util.Arrays;
37+
import java.util.concurrent.Future;
38+
39+
import javax.swing.JTextPane;
40+
import javax.swing.text.BadLocationException;
41+
import javax.swing.text.Document;
42+
43+
import org.scijava.Context;
44+
import org.scijava.thread.ThreadService;
45+
import org.scijava.ui.UIService;
46+
import org.scijava.ui.swing.sdi.SwingSDIUI;
47+
48+
/**
49+
* A behavioral test and benchmark of {@link SwingConsolePane}.
50+
*
51+
* @author Curtis Rueden
52+
*/
53+
public class SwingConsolePaneBenchmark {
54+
55+
// -- Main method --
56+
57+
/** A manual test drive of the Swing UI's console pane. */
58+
public static void main(final String[] args) throws Exception {
59+
final Context context = new Context();
60+
context.service(UIService.class).showUI();
61+
62+
System.out.print("Hello ");
63+
System.err.println("world!");
64+
65+
final int numThreads = 50;
66+
final int numOperations = 20;
67+
68+
final String[] streamLabels =
69+
{ ": {ERR} iteration #", ": {OUT} iteration #" };
70+
final String outLabel = streamLabels[1];
71+
final String errLabel = streamLabels[0];
72+
final int numStreams = streamLabels.length;
73+
74+
final int initialDelay = 500;
75+
76+
Thread.sleep(initialDelay);
77+
78+
final long start = System.currentTimeMillis();
79+
80+
// emit a bunch of output on multiple threads concurrently
81+
final ThreadService threadService = context.service(ThreadService.class);
82+
final Future<?>[] f = new Future<?>[numThreads];
83+
for (int t = 0; t < numThreads; t++) {
84+
final int tNo = t;
85+
f[t] = threadService.run(new Runnable() {
86+
87+
@Override
88+
public void run() {
89+
for (int i = 0; i < numOperations; i++) {
90+
System.out.print(str(tNo, outLabel, i) + "\n");
91+
System.err.print(str(tNo, errLabel, i) + "\n");
92+
}
93+
}
94+
});
95+
}
96+
97+
// wait for all output threads to finish
98+
for (int t = 0; t < numThreads; t++) {
99+
f[t].get();
100+
}
101+
102+
System.err.print("Goodbye ");
103+
System.out.println("cruel world!");
104+
105+
final long end = System.currentTimeMillis();
106+
System.out.println();
107+
System.out.println("Benchmark took " + (end - start) + " ms");
108+
109+
// Finally, check for completeness of output.
110+
// NB: We do this **also on the EDT** so that all output has flushed.
111+
final String completenessMessage = "Checking for completeness of output...";
112+
System.out.println();
113+
System.out.println(completenessMessage);
114+
threadService.queue(new Runnable() {
115+
116+
@Override
117+
public void run() {
118+
System.out.println();
119+
final SwingSDIUI ui =
120+
(SwingSDIUI) context.service(UIService.class).getVisibleUIs().get(0);
121+
final JTextPane textPane = ui.getConsolePane().getTextPane();
122+
final Document doc = textPane.getDocument();
123+
try {
124+
final String text = doc.getText(0, doc.getLength());
125+
final String[] lines = text.split("\n");
126+
Arrays.sort(lines);
127+
128+
int lineIndex = 0;
129+
assertEquals("", lines[lineIndex++]);
130+
assertEquals("", lines[lineIndex++]);
131+
for (int t = 0; t < numThreads; t++) {
132+
for (int s = 0; s < numStreams; s++) {
133+
for (int i = 0; i < numOperations; i++) {
134+
final String expected = str(t, streamLabels[s], i);
135+
final String actual = lines[lineIndex++];
136+
assertEquals(expected, actual);
137+
}
138+
}
139+
}
140+
assertTrue(lines[lineIndex++].startsWith("Benchmark took "));
141+
assertEquals(completenessMessage, lines[lineIndex++]);
142+
assertEquals("Goodbye cruel world!", lines[lineIndex++]);
143+
assertEquals("Hello world!", lines[lineIndex++]);
144+
assertEquals(lineIndex, lines.length);
145+
146+
System.out.println("Success! All output accounted for!");
147+
}
148+
catch (final BadLocationException exc) {
149+
exc.printStackTrace();
150+
}
151+
}
152+
});
153+
154+
}
155+
156+
// - Helper methods --
157+
158+
private static String str(final int t, final String separator, final int i) {
159+
return pad(t) + separator + pad(i);
160+
}
161+
162+
private static String pad(final int n) {
163+
return n < 10 ? "0" + n : "" + n;
164+
}
165+
166+
}

0 commit comments

Comments
 (0)